Skip to content

fix(governance): gate the real repository against its own lifecycle machine truth - #485

Merged
monkey1sai merged 4 commits into
mainfrom
fix/openspec-lifecycle-projection-gate
Aug 10, 2026
Merged

monkey1sai merged 4 commits into
mainfrom
fix/openspec-lifecycle-projection-gate

Conversation

@monkey1sai

@monkey1sai monkey1sai commented Aug 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

scripts/lib/openspec-machine-truth.mjs already owns a complete OpenSpec lifecycle comparator, but its CLI (scripts/tests/verify-openspec-machine-truth.mjs) requires a GitHub observation and an openspec list --json capture from a pinned binary. Both need network access and an external tool, so CI only ever runs that comparator against synthetic temporary fixtures. The only assertion that touched the real repository was a hand-written regex for one change id in scripts/tests/test-openspec-machine-truth.mjs.

Consequence: the three lifecycle truth sources could drift and no gate would refuse it. That is why the drift has repeatedly been corrected by hand after landing (#477, #481, #482).

This PR adds the repository-local subset of that comparison as a gate that needs no network, no git and no external binary, and wires it into the already required agent-governance check.

What it compares

Source Read from
change directories + > **Status: proposal markers openspec/changes/<id>/
machine ledger openspec/lifecycle-ledger.json
human-facing projection docs/plans/NOW.md lifecycle-ledger block

Mismatch codes: now_unexpected_change, now_change_missing, now_lifecycle_disagreement, ledger_current_without_directory, directory_without_ledger_row, duplicate_active_archive, change_entry_not_a_directory, change_directory_malformed, ledger_archived_without_archive_directory, archive_directory_without_ledger_row, archive_directory_duplicate, archive_directory_malformed, archive_entry_not_a_directory, proposal_missing, proposal_unreadable, proposal_marker_unreadable, proposal_marker_invalid, proposal_status_disagreement.

Failure discipline: anything an author can legitimately write is a mismatch (exit 2, names the row); only an input that cannot be read as declared is an input error (exit 3). One odd file never takes the whole required check down in place of pointing at the row that is wrong.

RED/GREEN evidence on the real repository

Against a real historical commit of main (c41e29d, the tip before #481):

openspec repository lifecycle: current=10 archived=110 now=11 change_dirs=10 archive_dirs=110
MISMATCH now_unexpected_change change=introduce-executable-architecture-contracts expected(lifecycle_ledger.current)=absent actual(now_projection)=active
openspec repository lifecycle FAILED with 1 mismatch(es).     exit=2

That drift was real, sat on main, and was only fixed later by #481. The existing scripts/tests/verify-openspec-lifecycle.ps1 returns exit 0 on the same tree (openspec lifecycle OK: non_deferred=5; deferred=5), which is the hole this PR closes.

Against this PR head and against origin/main (f1757b2) and the open PR branch chore/deploy-linux-test-skill: exit 0, all three sources agree. Injecting a one-word drift into docs/plans/NOW.md turns the required agent-governance static check red end to end (ASSERT FAILED: repository-scoped OpenSpec lifecycle parity fixtures pass).

Verification

Command Result
node --test scripts/tests/test-openspec-repository-lifecycle.mjs 36 pass / 0 fail / 0 skipped
node scripts/tests/verify-openspec-repository-lifecycle.mjs exit 0, current=10 archived=110 now=10 change_dirs=10 archive_dirs=110
pwsh -File scripts/tests/test-agent-governance-check.ps1 exit 0, all assertions passed
pwsh -File scripts/tests/invoke-powershell-static.ps1 exit 0
pwsh -File scripts/tests/scan-secret-patterns.ps1 exit 0
git diff --check clean

Reviewed by a three-layer adversarial pass (four lenses, cross-model refutation). Round-1/round-2 findings fixed in this head: non-directory and linked entries under openspec/changes are reported instead of silently filtered out (the Windows symlink-as-plain-file bypass); the CLI resolves process.argv[1] through links so it cannot become a silent no-op exit 0; the status vocabulary matches openspec-machine-truth.mjs exactly (held/completed/archived) so a legal row cannot take the gate down; link checks cover every path component below the root while a checkout legitimately reached through a junction is still evaluated; the fenced-block parse is linear instead of super-linear on an unterminated fence; near-miss and invalid markers are reported instead of silently defaulting to active.

Change Classification

Item Result
Change lane G
Behavior contract changed no
Requirement source existing contract: openspec/AGENTS.md lifecycle rules and docs/agents/self-referential-bootstrap.md

AI Coding Governance

Item Result
Linked issue none; repository-internal governance gap found while auditing lifecycle drift corrections #477 / #481 / #482
Requirement source existing contract: openspec/AGENTS.md lifecycle rules
CODEOWNERS / owner review /scripts/ and /openspec/ fall under @monkey1sai-blip; owner review required
GitNexus evidence UNAVAILABLE — gitnexus detect-changes --scope compare --base-ref main fails in this worktree with Multiple repositories indexed and the worktree has no .gitnexus/run.cjs; per docs/agents/gitnexus-usage.md this is declared unavailable, not passed. Blast radius is bounded by construction: three new files, no existing symbol modified, no rename; the only edit to existing code appends two commands to the end of scripts/tests/test-agent-governance-check.ps1 and one Assert-FileContains line
Browser E2E evidence not applicable — no frontend product path changed; .github/workflows/ci.yml changed-path classifier does not select design-semantic-visual or functional-runtime-conv for these paths
Agent workflow changed? yes in effect, no in YAML — no workflow file is modified; the required agent-governance static check gains two new commands
Required checks expected agent-governance, agent governance contracts, powershell static analysis, secret pattern scan, root contracts and fakes, changed path classifier, pr-metadata-contract-diagnostic, governance-base-audit

Self-referential bootstrap

Item Result
Self-referential bootstrap no

scripts/tests/test-agent-governance-check.ps1 is on the Get-SelfReferentialMechanismPaths list, so the declaration is required. It is no because there is no bootstrap paradox here: the file is not in SelfReferentialAdjudicatorPaths, no workflow YAML, adjudicator, deploy path or evidence harness is modified, and the pre-change mechanism validates the post-change behaviour completely — running the edited script is itself the evidence. The change deliberately avoided the mechanism surface for exactly this reason: the new comparator mirrors the marker/status contract from scripts/lib/openspec-machine-truth.mjs instead of importing it, because importing would pull mechanism surface into a read-only helper and open bootstrap debt that would block the next mechanism PR. The bootstrap ledger currently carries zero open entries and this PR adds none.

Known boundaries

  • Archived proposal markers are not read here; scripts/tests/verify-openspec-lifecycle.ps1 already refuses a deferred marker stored in the completed archive.
  • Task-ledger counts, evidence refs, subject_commit freshness and GitHub PR state stay with scripts/lib/openspec-machine-truth.mjs; the WIP budget stays with verify-openspec-lifecycle.ps1.
  • The proposal-marker parse is a pinned mirror rather than a shared import, so a future change to openspec-machine-truth.mjs must update both. This is a deliberate trade against opening bootstrap debt.
  • scripts/**/*.mjs is not linted anywhere in CI (no root ESLint config); pre-existing repository gap, not introduced here.
  • Full completion claimed: no. This gate covers repository-local lifecycle parity only.

Summary by CodeRabbit

  • New Features

    • Added an offline repository lifecycle verifier that checks consistency across lifecycle records, current projections, proposals, and archived changes.
    • Supports human-readable and JSON reports with clear success, mismatch, and input-error outcomes.
    • Provides deterministic validation without network access or external tooling.
  • Documentation

    • Updated repository governance guidance and verification checklists to include lifecycle consistency checks.
  • Tests

    • Added comprehensive coverage for valid, inconsistent, malformed, unsafe, and symlinked repository states.

…achine truth

scripts/lib/openspec-machine-truth.mjs already owns a complete lifecycle
comparator, but its CLI needs a GitHub observation and a pinned openspec
binary, so CI could only ever exercise it against synthetic temporary
fixtures. The one assertion that touched the real tree was a hand-written
regex for a single change id, so drift between openspec/changes, the
machine ledger and the NOW projection was corrected by hand after landing
(#477, #481, #482) instead of being refused at PR time.

This adds the repository-local subset of that comparison as a gate that
needs no network, git or external binary, and wires it into the already
required agent-governance check. It compares the change directories and
their proposal markers, openspec/lifecycle-ledger.json, and the NOW
projection, reports disagreement rather than resolving it, and fails
closed on any input it cannot read as declared.

Proven on a real historical commit: run against main at c41e29d the gate
reports now_unexpected_change for introduce-executable-architecture-contracts,
drift that existed on main and was only fixed later by #481.

Claude-Session: cowork scheduled task ai-bim-geo

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings August 10, 2026 11:10
@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@monkey1sai, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 15 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ae1d47a-765e-453d-b0ea-cd6ea7b91450

📥 Commits

Reviewing files that changed from the base of the PR and between 82d1c70 and 992c140.

📒 Files selected for processing (3)
  • scripts/lib/openspec-repository-lifecycle.mjs
  • scripts/tests/test-openspec-repository-lifecycle.mjs
  • scripts/tests/verify-openspec-repository-lifecycle.mjs
📝 Walkthrough

Walkthrough

Adds network-free OpenSpec lifecycle parity validation. The verifier parses repository-local lifecycle sources, compares current and archived state, reports deterministic mismatches, and runs through governance checks.

Changes

Repository lifecycle parity

Layer / File(s) Summary
Lifecycle input contracts and parsers
scripts/lib/openspec-repository-lifecycle.mjs
Adds bounded filesystem validation and strict parsers for the lifecycle ledger, NOW.md, and proposal status markers.
Repository observation and parity comparison
scripts/lib/openspec-repository-lifecycle.mjs
Collects lifecycle sources and compares active changes, archives, ledger rows, NOW rows, directories, and proposal markers.
CLI and governance integration
scripts/tests/verify-openspec-repository-lifecycle.mjs, openspec/AGENTS.md
Adds formatted CLI reports, fail-closed exit codes, direct invocation support, and the lifecycle verifier to the governance checklist.
Lifecycle parity validation
scripts/tests/test-openspec-repository-lifecycle.mjs, scripts/tests/test-agent-governance-check.ps1
Adds parser, comparison, safety, CLI, linked-path, fixture, and current-repository integration tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant VerifierCLI
  participant LifecycleEvaluator
  participant RepositorySources
  participant LifecycleComparator
  VerifierCLI->>LifecycleEvaluator: evaluate repository root
  LifecycleEvaluator->>RepositorySources: read ledger, NOW, changes, archives, proposals
  RepositorySources-->>LifecycleEvaluator: lifecycle observation
  LifecycleEvaluator->>LifecycleComparator: compare observed state
  LifecycleComparator-->>VerifierCLI: parity report and exit status
Loading

Possibly related PRs

Suggested reviewers: monkey1sai-blip

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding a governance gate that checks the repository against its lifecycle machine truth.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/openspec-lifecycle-projection-gate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 82d1c70026

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/tests/verify-openspec-repository-lifecycle.mjs
Comment thread scripts/lib/openspec-repository-lifecycle.mjs Outdated
Comment thread scripts/lib/openspec-repository-lifecycle.mjs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR closes a governance hole in OpenSpec lifecycle enforcement. The existing full comparator (scripts/lib/openspec-machine-truth.mjs) can only run against synthetic fixtures in CI because its CLI requires a GitHub observation and a pinned openspec binary (network + external tool). As a result, drift between the three repository-local lifecycle truth sources — openspec/changes/<id>/ directories + proposal markers, openspec/lifecycle-ledger.json, and the docs/plans/NOW.md projection — was only ever corrected by hand after landing (#477, #481, #482). This change adds a network-free, git-free, binary-free parity gate and wires it into the already-required agent-governance check.

Changes:

  • New comparator library openspec-repository-lifecycle.mjs that reads the three repository-local sources and reports targeted mismatches (input errors vs. mismatches distinguished with distinct exit codes 3/2/0), deliberately mirroring the marker/status contract of openspec-machine-truth.mjs rather than importing it (to avoid self-referential mechanism-surface coupling).
  • New CLI verify-openspec-repository-lifecycle.mjs and a 36-case node --test fixture suite, wired into test-agent-governance-check.ps1.
  • Documents the new gate as a MUST in openspec/AGENTS.md.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
scripts/lib/openspec-repository-lifecycle.mjs New comparator: parses ledger/NOW/proposal markers, enforces symlink/DoS/schema bounds, emits deterministic mismatch reports.
scripts/tests/verify-openspec-repository-lifecycle.mjs New CLI wrapper with arg parsing, text/JSON rendering, exit-code discipline, and link-safe direct-invocation detection.
scripts/tests/test-openspec-repository-lifecycle.mjs 36 fixtures proving every mismatch/input-error code fires and pointing the comparator at the real repository.
scripts/tests/test-agent-governance-check.ps1 Wires the fixtures + real-repo verify into the required agent-governance static check.
openspec/AGENTS.md Documents the three-source parity requirement and the new verify command.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
scripts/tests/test-openspec-repository-lifecycle.mjs (1)

36-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Write the BOM as an escape sequence.

The literal holds an invisible U+FEFF character. An editor, a formatter, or a .gitattributes normalisation can drop it. The BOM test at Line 557 would then still pass while proving nothing. '\uFEFF' is visible in review and stable in transit.

♻️ Proposed change
-const BOM = '';
+const BOM = '\uFEFF';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/tests/test-openspec-repository-lifecycle.mjs` at line 36, Update the
BOM constant to use the explicit \uFEFF escape sequence instead of embedding the
invisible character literally, while preserving the existing BOM test behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/lib/openspec-repository-lifecycle.mjs`:
- Around line 466-470: Update the mismatch() call for
directory_without_ledger_row so the source labels and values report
openspec_changes as expected/present and lifecycle_ledger.current as
actual/absent, matching the directory’s presence and missing ledger row. Keep
the mismatch type, entry.name, and message unchanged.

---

Nitpick comments:
In `@scripts/tests/test-openspec-repository-lifecycle.mjs`:
- Line 36: Update the BOM constant to use the explicit \uFEFF escape sequence
instead of embedding the invisible character literally, while preserving the
existing BOM test behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 502bf2e6-e221-4e9c-872d-5d8cae1429be

📥 Commits

Reviewing files that changed from the base of the PR and between f1757b2 and 82d1c70.

📒 Files selected for processing (5)
  • openspec/AGENTS.md
  • scripts/lib/openspec-repository-lifecycle.mjs
  • scripts/tests/test-agent-governance-check.ps1
  • scripts/tests/test-openspec-repository-lifecycle.mjs
  • scripts/tests/verify-openspec-repository-lifecycle.mjs

Comment thread scripts/lib/openspec-repository-lifecycle.mjs
monkey1sai and others added 2 commits August 10, 2026 19:29
…y gate

Codex tri-adversarial gate on PR #485 returned SHIP with five non-blocking
survivors. Four are closed here; the fifth is disclosed.

- L1-1: the current-change cap counted the mandatory archive namespace, so
  the filesystem observation could not represent the same 512-row boundary
  the NOW parser accepts. One raw slot is now reserved and the filtered set
  is bounded instead.
- SEC-001: path-derived values reached the text renderers verbatim, so a
  directory name carrying a newline or ESC byte could forge extra report
  lines. Values with control characters are now JSON-quoted; JSON output is
  unchanged.
- TG-001: the input_too_large budgets had no coverage. Added the accepted
  boundary and one past it for change directories, NOW rows, ledger rows and
  file bytes.
- TG-003: the non-regular proposal.md predicate was only reachable through a
  skippable symlink fixture. Added a portable directory-named-proposal.md
  case.

Not closed: SEC-002 (readdirSync materialises the full Dirent list before the
entry cap is applied). Rated low by the apex; incremental opendirSync
iteration is a larger refactor and is left as disclosed follow-up.

Claude-Session: cowork scheduled task ai-bim-geo

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…gate run

Second Codex tri-adversarial run on head 40d93c6 returned SHIP with four
survivors; the two medium ones were real fail-open defects in the marker
parser, which this gate promotes to merge-blocking authority.

- L1-1: the canonical marker regex had no trailing token boundary, so
  `> **Status: active123**` captured `active` and read as a clean active
  row. The status token must now end at `**`, whitespace or end of line;
  anything else is reported as an unreadable marker.
- L1-2: near-miss detection ran only when no canonical marker was found, so a
  proposal carrying two disagreeing declarations resolved silently to the
  canonical one. Every bolded status-like line in the prologue is now
  inventoried and an unconsumed one is reported even beside a canonical marker.
- L1-3: the raw namespace budget is now separate from the canonical change
  budget, so one stray entry beside a full change set stays a targeted
  `change_entry_not_a_directory` mismatch instead of a generic budget error.
- TG-1: added a control-character assertion for the text input-error path.

Verified against all 120 real proposals: the ten current ones are unchanged;
only one archived proposal reads `invalid` (`Status: closeout reconciled`),
and archived markers are outside this gate's scope.

Claude-Session: cowork scheduled task ai-bim-geo

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ee148353f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/lib/openspec-repository-lifecycle.mjs Outdated

@codex-tri-adversarial-bot codex-tri-adversarial-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex Tri-Adversarial Bot

Automated tri-adversarial ship-gate (L0 terra triage / L1 tier-routed lens fanout / L2 refute-by-default / L3 sol apex — Codex models).
Mapped event: COMMENT


Codex Tri-Adversarial ship-gate — PR #485

  • Repo head: fix/openspec-lifecycle-projection-gate @ 1ee1483
  • Base: main @ f1757b2
  • Files changed: 5
  • Engine: four-model tri-adversarial gate on Codex — L0 triage gpt-5.6-terra/low; L1 lens finders routed gpt-5.6-terra/low → gpt-5.6-luna/medium → gpt-5.5/xhigh (security floor gpt-5.5); L2 refute-by-default gpt-5.5/xhigh, top-tier findings refuted by gpt-5.6-sol/xhigh (every refutation cross-model); L3 apex gpt-5.6-sol/max. 誠實聲明:層級與 Claude 三層 gate 同構(terra≈haiku、luna≈sonnet、gpt-5.5≈opus、sol≈fable),但模型池是 Codex 的,非 Anthropic 的。

Verdict

SHIP

  • 阻擋門檻 severity: critical, high
  • mapped GitHub event: COMMENT
  • ℹ️ 判定為 SHIP,但刻意不送 APPROVE:GitHub App 的 approving review 不計入 required_approving_review_count(2026-07-31 實測)。本報告是證據,approving 那一票請由真人帳號投。

Difficulty & routing

  • overall: high (source: terra-triage)
  • lens tiers: correctness→gpt-5.5, security→gpt-5.5, simplification→gpt-5.5, test-gap→gpt-5.5

Layer stats

  • L1: raw=4 deduped=4 finder_failures=0
  • L2: confirmed=2 refuted=2 unverified=0
  • L3 final: 2

Findings (final, after apex)

[low] Raw namespace entry cap is not pinned

  • id: TG-001 lens: test-gap file: scripts/tests/test-openspec-repository-lifecycle.mjs
  • provenance: finder=gpt-5.5 refuter=gpt-5.6-sol (cross-model-guard) L2=confirmed
  • evidence: The implementation separately caps raw openspec/changes entries at MAX_CURRENT_CHANGES + 65, but tests exercise only the 512/513 canonical-directory boundary and 512 canonical directories plus one malformed entry. No fixture exceeds the 577-entry raw cap.
  • why: Removing or materially shifting the raw namespace cap would leave the current suite passing. This leaves the deliberately bounded malformed-entry path without a regression guard.
  • proposed fix: Create 512 canonical changes plus 65 malformed entries and the mandatory archive directory (578 raw entries), then assert RepositoryLifecycleInputError with code input_too_large. Optionally also pin the accepted 577-entry boundary.

[low] Spawned CLI input-error path lacks coverage

  • id: TG-002 lens: test-gap file: scripts/tests/test-openspec-repository-lifecycle.mjs
  • provenance: finder=gpt-5.5 refuter=gpt-5.6-sol (cross-model-guard) L2=confirmed
  • evidence: Spawned-process tests cover parity and mismatch only. Input errors are tested through main(..., sink), which bypasses the default { out: process.stdout, err: process.stderr } streams used by direct CLI invocation.
  • why: The existing tests would not catch a regression that routes process-level input errors to stdout while retaining the correct exit code. Direct invocation and nonzero process.exitCode are already covered, so the surviving gap is specifically default stderr/stdout routing for exit 3.
  • proposed fix: Spawn the CLI against a malformed ledger or missing required input and assert status 3, an expected error on stderr, and empty stdout.

Killed (did not survive L2/L3)

  • L1-1 [medium] Canonical marker parser accepts unclosed bold status markers — The finding assumes the contract requires ** immediately after the status token, but the diff does not establish that. The governing text allows Status: deferred <日期>, and the explicit test confirms trailing date metadata is intentional. This gate extracts lifecycle status; it does not claim to
  • L1-SIMP-1 [medium] New gate duplicates lifecycle parsing/comparison logic instead of extracting a shared core — The finding demonstrates only a narrow mirrored contract (CHANGE_ID, STATUS, and baseline marker semantics), not duplicated lifecycle comparison logic broadly. The new parser is materially different: it adds near-miss detection, token-boundary hardening, and detection of multiple status-like dec

Summary

KEEP both survivors as low-severity test gaps. Add one boundary test for the distinct raw namespace cap and one spawned malformed-input test that verifies exit 3 and stderr/stdout routing.

Agent calls

  • 10/10 ok, engine wall-clock 396.5s

VERDICT

SHIP

VERDICT: SHIP

…ion marker counting

Both from PR #485 review threads.

- The `directory_without_ledger_row` record had its observations reversed: the
  directory is present in `openspec/changes` and absent from the ledger, so
  the record now names the directory as the observed value and `absent` as
  what the ledger was expected to carry.
- Status-like declarations are now counted per declaration rather than per
  line, so `> **Status: active** **Status: deferred**` on a single blockquote
  line is reported as an unreadable marker instead of resolving to `active`.

Claude-Session: cowork scheduled task ai-bim-geo

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 992c140ca7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/lib/openspec-repository-lifecycle.mjs
Comment thread scripts/lib/openspec-repository-lifecycle.mjs
Comment thread scripts/lib/openspec-repository-lifecycle.mjs

@codex-tri-adversarial-bot codex-tri-adversarial-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex Tri-Adversarial Bot

Automated tri-adversarial ship-gate (L0 terra triage / L1 tier-routed lens fanout / L2 refute-by-default / L3 sol apex — Codex models).
Mapped event: COMMENT


Codex Tri-Adversarial ship-gate — PR #485

  • Repo head: fix/openspec-lifecycle-projection-gate @ 992c140
  • Base: main @ f1757b2
  • Files changed: 5
  • Engine: four-model tri-adversarial gate on Codex — L0 triage gpt-5.6-terra/low; L1 lens finders routed gpt-5.6-terra/low → gpt-5.6-luna/medium → gpt-5.5/xhigh (security floor gpt-5.5); L2 refute-by-default gpt-5.5/xhigh, top-tier findings refuted by gpt-5.6-sol/xhigh (every refutation cross-model); L3 apex gpt-5.6-sol/max. 誠實聲明:層級與 Claude 三層 gate 同構(terra≈haiku、luna≈sonnet、gpt-5.5≈opus、sol≈fable),但模型池是 Codex 的,非 Anthropic 的。

Verdict

SHIP

  • 阻擋門檻 severity: critical, high
  • mapped GitHub event: COMMENT
  • ℹ️ 判定為 SHIP,但刻意不送 APPROVE:GitHub App 的 approving review 不計入 required_approving_review_count(2026-07-31 實測)。本報告是證據,approving 那一票請由真人帳號投。

Difficulty & routing

  • overall: critical (source: terra-triage)
  • lens tiers: correctness→gpt-5.5, security→gpt-5.5, simplification→gpt-5.5, test-gap→gpt-5.5

Layer stats

  • L1: raw=6 deduped=6 finder_failures=0
  • L2: confirmed=3 refuted=3 unverified=0
  • L3 final: 3

Findings (final, after apex)

[medium] Bolded ordinary Status prose is treated as a lifecycle marker near-miss

  • id: L1-COR-002 lens: correctness file: scripts/lib/openspec-repository-lifecycle.mjs line: 336
  • provenance: finder=gpt-5.5 refuter=gpt-5.6-sol (cross-model-guard) L2=confirmed
  • evidence: The final statusLike inventory counts every **Status\b occurrence on a blockquote line, while the canonical regex requires **Status:. Re-executing the exact final logic yields near-miss for > **Status quo** is unacceptable., but active for the unbolded control and > **Implementation status** — partial.
  • why: The documented canonical lifecycle syntax includes a colon; ordinary bold prose beginning with “Status” is not necessarily a declaration. This false positive becomes proposal_marker_unreadable and fails the required governance gate for an otherwise consistent active proposal.
  • proposed fix: Restrict statusLike to marker-shaped text, such as Status followed by a colon or a recognized lifecycle token. Add tests proving > **Status quo** ... remains ordinary prose while malformed forms such as **Status:** deferred, **Status : deferred**, and missing-colon declarations still fail closed as intended.

[low] Text renderer escaping is tested for newline only, not ESC/control bytes it explicitly claims to defend

  • id: TG-002 lens: test-gap file: scripts/tests/test-openspec-repository-lifecycle.mjs line: 720
  • provenance: finder=gpt-5.5 refuter=gpt-5.6-sol (cross-model-guard) L2=confirmed
  • evidence: Both renderer-hardening tests inject \n. The implementation and its comment explicitly cover the entire U+0000–U+001F range, including ESC, but no test exercises \x1b; therefore a regression narrowing the predicate to newline would leave both tests green.
  • why: ESC is the terminal-control threat specifically called out by the implementation. Without an ESC assertion, the tests do not protect the full security behavior the change claims to establish.
  • proposed fix: Add \x1b[31m to a mismatch value and/or input-error field, then assert text output contains escaped \u001b, contains no raw ESC byte, and remains one physical line.

[low] No boundary test for the raw namespace entry cap added above the canonical change budget

  • id: TG-003 lens: test-gap file: scripts/tests/test-openspec-repository-lifecycle.mjs line: 773
  • provenance: finder=gpt-5.5 refuter=gpt-5.6-sol (cross-model-guard) L2=confirmed
  • evidence: MAX_CHANGE_NAMESPACE_ENTRIES is 512 + 65, and listChildEntries counts the mandatory archive entry before filtering it. Existing tests cover 512 canonical changes and 512 plus one malformed entry, but never reach the 577-entry raw cap.
  • why: The canonical-count tests cannot detect drift in this separate raw-entry budget. Raising it or lowering it while still accepting the single-stray fixture would remain untested.
  • proposed fix: Use the corrected boundaries including archive: verify 512 canonical changes + 64 malformed entries + archive (577 raw entries) reaches targeted mismatch reporting, while 512 + 65 malformed + archive (578) throws input_too_large.

Killed (did not survive L2/L3)

  • L1-COR-001 [high] New fixture uses literal newline text and makes the required node test fail — The finding overlooks Patch 4’s per-declaration counting. Even with literal \\n, the direct parser input contains one canonical declaration but two status-like declarations, so it returns near-miss, not active. In the repository fixture, status.includes('\n') is false, so withRepository pa
  • TG-001 [medium] parseArguments can silently ignore a trailing flag value in untested argument shapes — The loop processes every argv position in two-element steps. An odd trailing token is visited with value === undefined; an even surplus pair is visited with an unrecognized flag. Both throw invalid_argument, and main maps that to EXIT_INPUT_ERROR. Existing tests already exercise those same
  • TG-004 [medium] Per-declaration status counting lacks a canonical-on-same-line invalid-marker case — The finding assumes both same-line spans are canonical, but the canonical grammar is anchored at the start of a blockquote line (^>...**Status:). In > **Status: active** **Status: deferred**, only the first span is canonical; the second is an unconsumed status-like near-miss. The added unit test

Summary

KEEP all three survivors. L1-COR-002 remains a medium correctness defect because valid bold prose can falsely fail the required lifecycle gate. TG-002 and TG-003 remain low-severity gaps covering the explicit ESC-hardening behavior and the separate raw-namespace cap boundary.

Agent calls

  • 12/12 ok, engine wall-clock 664.7s

VERDICT

SHIP

VERDICT: SHIP

@monkey1sai-blip monkey1sai-blip left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved by monkey1sai-blip (the reviewer account pinned by the repo's merge governance).

Submitted through scripts/blip_review.py — a scripted approval carrying the operator's authority, pinned to head 992c140ca7e2ea627d1df4e178fe6982fa56e4ee. This is the mechanism the GitHub App cannot satisfy: an App's approving review does not count toward required_approving_review_count.

@monkey1sai
monkey1sai merged commit 4ee3f0c into main Aug 10, 2026
20 of 30 checks passed
@monkey1sai
monkey1sai deleted the fix/openspec-lifecycle-projection-gate branch August 10, 2026 12:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants