diff --git a/.github/workflows/bitcode-gate-quality.yml b/.github/workflows/bitcode-gate-quality.yml index 51b2c8921..9dd18b58f 100644 --- a/.github/workflows/bitcode-gate-quality.yml +++ b/.github/workflows/bitcode-gate-quality.yml @@ -44,6 +44,8 @@ jobs: node scripts/check-bitcode-spec-family.mjs --version V28 --mode draft --current-target V27 node scripts/check-bitcode-canon-posture-drift.mjs --active-canon V27 --draft-target V28 node scripts/check-bitcode-canonical-inputs.mjs --current-target V27 + node scripts/check-v28-metadevelopment-readiness.mjs --skip-branch-check + node scripts/promote-bitcode-canon.mjs --version V28 --commit HEAD --dry-run - name: Validate source casing and imports run: | diff --git a/.github/workflows/v28-canon-promotion.yml b/.github/workflows/v28-canon-promotion.yml index 48ee5bd7d..5e9d8a47a 100644 --- a/.github/workflows/v28-canon-promotion.yml +++ b/.github/workflows/v28-canon-promotion.yml @@ -60,16 +60,17 @@ jobs: node scripts/check-bitcode-spec-family.mjs --version V28 --mode draft --current-target V27 node scripts/check-bitcode-canon-posture-drift.mjs --active-canon V27 --draft-target V28 node scripts/check-bitcode-canonical-inputs.mjs --current-target V27 - node scripts/check-bitcode-spec-family.mjs --version V28 --mode promoted --current-target V28 --skip-pointer-check - node scripts/check-bitcode-canonical-inputs.mjs --current-target V28 --skip-pointer-check + node scripts/check-v28-metadevelopment-readiness.mjs --promotion-mode --skip-branch-check + node scripts/promote-bitcode-canon.mjs --version V28 --commit HEAD --dry-run elif [ "$POINTER" = "V28" ]; then node scripts/check-bitcode-spec-family.mjs --version V28 --mode promoted --current-target V28 node scripts/check-bitcode-canonical-inputs.mjs --current-target V28 + node scripts/check-v28-metadevelopment-readiness.mjs --promotion-mode --skip-branch-check else echo "Unexpected BITCODE_SPEC.txt pointer: $POINTER" >&2 exit 1 fi - npm --prefix protocol-demonstration run test + npm --prefix protocol-demonstration run test:v28-mvp-qa pnpm --filter @bitcode/pipeline-asset-pack typecheck pnpm --filter @bitcode/pipeline-hosts typecheck pnpm --filter @bitcode/pipeline-asset-pack exec jest --config jest.config.cjs --passWithNoTests --forceExit @@ -83,7 +84,7 @@ jobs: --runInBand git diff --check - - name: Promote BITCODE_SPEC pointer + - name: Promote V28 canon files if: >- ${{ github.event_name == 'pull_request' && @@ -96,9 +97,19 @@ jobs: echo "BITCODE_SPEC.txt already points to V28." exit 0 fi - printf "V28\n" > BITCODE_SPEC.txt + PROOF_SOURCE_COMMIT="$(git rev-parse HEAD)" + npm run promote:canon -- --version V28 --commit "$PROOF_SOURCE_COMMIT" git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add BITCODE_SPEC.txt + git add \ + BITCODE_SPEC.txt \ + BITCODE_SPEC_V28.md \ + BITCODE_SPEC_V28_DELTA.md \ + BITCODE_SPEC_V28_NOTES.md \ + BITCODE_SPEC_V28_PARITY_MATRIX.md \ + BITCODE_SPEC_V28_PROVEN.md \ + protocol-demonstration/src/canon-posture.js \ + protocol-demonstration/README.md \ + .bitcode git commit -m "Promote Bitcode canon to V28" git push origin "HEAD:$HEAD_BRANCH" diff --git a/BITCODE_SPEC_V28.md b/BITCODE_SPEC_V28.md index c3846cab0..f48b629dd 100644 --- a/BITCODE_SPEC_V28.md +++ b/BITCODE_SPEC_V28.md @@ -759,21 +759,66 @@ Implementation evidence: `uapi/tests/terminalOperationalHealthPanel.test.tsx`; package-level BTD tests are runnable through `pnpm -C packages/btd test`. -### Gate 8: V28 Promotion Proof +### Gate 8: V28 Metadevelopment And Promotion Proof Purpose: -Promote V28 only after commercial Protocol/Terminal MVP QA, Terminal readiness productization, MCP/ChatGPT App MVP readiness, tests, proof, and documentation are closed. +Close the repository metadevelopment standard that V28 introduced: version branches, gate-prefixed branches, quality commit messages, maintained gate workflows, promotion workflows, and a carryforward audit that prevents remaining commercial-product work from being hidden inside promotion prose. Acceptance criteria: -- V28 SPEC, DELTA, NOTES, PARITY, and PROVEN are synchronized. -- package/API, ORM, protocol-demonstration, UAPI route, Terminal UI, and build checks pass. -- unversioned-route scan passes. -- V29 Terminal depth, V30 Protocol/BTD hardening, V31 Auxillaries depth, V32 provation/testing depth, V33 interface depth beyond the V28 MVP, V34 deployment depth, V35 telemetry/documenting depth, and V36+ Exchange/Conversations depth are explicitly staged. -- a V28 version-branch pull request into `main` runs promotion-grade - automation; only after those validations pass does automation commit the - standalone `BITCODE_SPEC.txt` pointer change. -- `BITCODE_SPEC.txt` remains V27 until the final automated promotion step. +- branch policy is source-documented: V28 work integrates through + `version/v28`; gate work occurs on `v28/gate-N-*` branches; gate branches + merge into the version branch; only the complete version branch requests + promotion into `main`. +- contribution policy is source-documented: commits are quality-grouped and + titled descriptively enough for promotion proof and future operators. +- the gate workflow checks draft canon posture, import/casing hygiene, package + typechecks/tests, focused UAPI route/Terminal tests, demonstration tests, + diff hygiene, metadevelopment readiness, and a dry-run V28 promotion plan. +- the V28 promotion workflow triggers only for `version/v28` pull requests into + `main`, runs promotion-grade checks, executes the canonical promotion script, + generates/commits `BITCODE_SPEC_V28_PROVEN.md`, and advances + `BITCODE_SPEC.txt` only in the automated promotion commit. +- the V28 parity matrix includes a rich carryforward audit for the commercial + product gates that follow Gate 8: Depositing, Read Request, Read-Need + synthesis/review/resynthesis, Finding Fits, AssetPack preview, BTC + settlement/read-license, PR delivery, Terminal UX, public/internal + documentation, proof coverage, and local/live validation. +- unversioned-route scan passes and `BITCODE_SPEC.txt` remains V27 until the + final automated promotion step. + +### Gate 9: Commercial Depositing And Depository Evidence + +Gate 9 owns source deposit productization after the metadevelopment gate: +repository inventory, deposit admission, depository evidence storage, +measurement vectors, wallet/depositor boundaries, search-index population, and +Terminal visibility for deposited source materials. + +### Gate 10: Read Request To Read-Need Comprehension + +Gate 10 owns the enterprise Reading start: request Read, synthesize a +reviewable Need through `ReadNeedComprehensionSynthesis`, allow feedback and +resynthesis, persist accepted Need truth, and block Finding Fits until the Need +is reviewed. + +### Gate 11: Finding Fits And Source-Safe AssetPack Preview + +Gate 11 owns `ReadFindingFitsSynthesis`: depository search, candidate fit +ranking, fitting deposits as synthesis context, AssetPack measurement, source- +safe preview, Share-to-Fee quote, and proof-rich Terminal stream visibility. + +### Gate 12: Settlement, Rights, Delivery, And Reconciliation + +Gate 12 owns BTC settlement, depositor/reader ownership boundaries, read-license +rows, ledger/database synchronization, protected-source unlock, and PR delivery +of the purchased AssetPack into the Reading repository. + +### Gate 13: Commercial Product Closure And Promotion Readiness + +Gate 13 owns final commercial-product closure: website application UX, package +abstractions, tests, local live validation, staging-testnet readback evidence, +public/internal documentation, generated proof coverage, and the final +`version/v28` promotion pull request. ## Draft Boundary diff --git a/BITCODE_SPEC_V28_DELTA.md b/BITCODE_SPEC_V28_DELTA.md index a2deee49b..217217c04 100644 --- a/BITCODE_SPEC_V28_DELTA.md +++ b/BITCODE_SPEC_V28_DELTA.md @@ -176,9 +176,19 @@ Adds registry-derived organization, MCP API, ChatGPT App access decisions, and p Adds Terminal-operated deployment lane, telemetry, upgrade, migration, and provider readiness state. -### Gate 8: V28 Promotion Proof +### Gate 8: V28 Metadevelopment And Promotion Proof -Adds final proof, build/test, unversioned route, and scope-staging closure. +Adds branch policy, quality commit discipline, gate workflow hardening, +canonical promotion automation, V28 promotion-script support, unversioned-route +checking, deterministic ledgerized model posture checks, and a carryforward +audit for the product gates that follow. + +### Gate 9+: Commercial Product Closure Gates + +Gate 9 and later gates close the remaining commercial product flow: Depositing, +Read Request, Read-Need synthesis/review/resynthesis, Finding Fits, AssetPack +preview, BTC settlement, read-license/right transfer, protected-source unlock, +PR delivery, Terminal UX, documentation, proof coverage, and live validation. ## Later Version Handoff diff --git a/BITCODE_SPEC_V28_NOTES.md b/BITCODE_SPEC_V28_NOTES.md index 9d25663f2..364321117 100644 --- a/BITCODE_SPEC_V28_NOTES.md +++ b/BITCODE_SPEC_V28_NOTES.md @@ -525,11 +525,34 @@ The minimum useful V28 gate plan is Protocol/Terminal-MVP-first: live Reading pipeline telemetry. Placeholder mocks are not acceptance evidence. -8. **Gate 8: V28 Promotion Proof** - - SPEC, DELTA, NOTES, PARITY, and PROVEN exist. - - Terminal tests, MCP/ChatGPT App tests, package/API tests, route tests, UAPI build, and demonstration tests pass. +8. **Gate 8: V28 Metadevelopment And Promotion Proof** + - Branching policy, gate branch naming, quality commit-message discipline, + gate workflow checks, and V28 promotion workflow checks are documented and + source-validated. + - The V28 promotion workflow is maintained as the only path that advances + `BITCODE_SPEC.txt`; it runs promotion-grade validation, executes the + canonical promotion script, generates `BITCODE_SPEC_V28_PROVEN.md`, and + commits promotion artifacts back to `version/v28`. + - The parity matrix carries an explicit product-gate carryforward audit so + Depositing, Read Request, Read-Need, Finding Fits, AssetPack preview, + settlement, delivery, Terminal UX, documentation, proof coverage, and live + validation work cannot be hidden as vague future cleanup. - V29 Terminal depth, V30 Protocol/BTD hardening, V31 Auxillaries depth, V32 provation/testing depth, V33 interface depth beyond the V28 MVP, V34 deployment depth, V35 telemetry/documenting depth, and V36+ Exchange/Conversations depth are explicitly staged rather than mixed into V28. +## Gate 8 metadevelopment closure notes + +Gate 8 is not a commercial-product feature gate. It standardizes how the +remaining V28 gates close: gate-prefixed branches merge into `version/v28`, +the version branch is the only branch that can request promotion into `main`, +and promotion automation must either generate and commit the canonical proof +appendix or fail closed. + +Gate 8 also separates promotion mechanics from product closure. Subsequent V28 +gates still own the commercial product flow: source Depositing, Read Request, +Need synthesis/review/resynthesis, Finding Fits over the depository, source-safe +AssetPack preview, BTC settlement, read-license/right transfer, pull-request +delivery, Terminal usability, proof coverage, and local/live validation. + ## Non-Goals For V28 - V28 does not implement or QA Exchange product behavior, broad order-book depth, external market routing, wrapper liquidity, or third-party marketplace integration. @@ -543,8 +566,10 @@ The minimum useful V28 gate plan is Protocol/Terminal-MVP-first: Before V28 implementation closes, rerun at minimum: - `cat BITCODE_SPEC.txt` +- `pnpm run check:v28-metadevelopment` - `find uapi/app/api -path '*v[0-9]*' -print | sort` - `rg -n 'gap blocking|partial blocking|not started|not promoted|not generated yet' BITCODE_SPEC_V28*` +- `node scripts/promote-bitcode-canon.mjs --version V28 --commit HEAD --dry-run` - `pnpm -C packages/api build` - `pnpm -C packages/orm build` - `npm --prefix protocol-demonstration run test:v27-crypto` diff --git a/BITCODE_SPEC_V28_PARITY_MATRIX.md b/BITCODE_SPEC_V28_PARITY_MATRIX.md index 73409ae32..88cdf6ab8 100644 --- a/BITCODE_SPEC_V28_PARITY_MATRIX.md +++ b/BITCODE_SPEC_V28_PARITY_MATRIX.md @@ -149,6 +149,41 @@ commercial pipeline names are canonical for this gate: | Mock parity and typed envelopes | `reading-pipeline-contract.test.ts`, `bounded-structured-inference.test.ts`, UAPI route/stream tests, pipeline-host harness tests, package/UAPI focused suites | closed for current gate | Mocked phases emit the same pipeline, phase, agent, PTRR step, ThricifiedGeneration ids, prompt, tool, raw-output, parsed-output, timing/usage, and fail-closed envelopes as live bounded runs; untyped placeholders are not acceptance evidence. | | Local live validation | `uapi/package.json` `dev:staging`, `.env.local` staging-testnet env, Vercel Sandbox OIDC, OpenAI API, Supabase staging, `scripts/verify-v28-pipeline-readback.mjs` | closed for current gate | Local UAPI real OpenAI Read-Need synthesis and staging-testnet readback verifier prove request Read -> review Need -> request Fit -> review preview -> buy/settlement posture through the accepted source-bound run. Both REST and DB readback paths report `ready_for_v28_result_review`; the only warning is the intentionally substituted missing generic `phase_executions` table because the canonical deliverable phase hierarchy is populated. | +## V28 Metadevelopment Parity Matrix + +Gate 8 closes repository operating standards before the remaining V28 product +gates continue. These rows are about how Bitcode development is promoted, +proved, and merged; they do not replace the commercial product closure gates. + +| Gate 8 metadevelopment surface | Source evidence | Current V28 judgment | Closure evidence required | +| --- | --- | --- | --- | +| Branching and gate workflow standard | `README.md`, `AGENTS.md`, protected `main`, branch examples `version/v28` and `v28/gate-N-*` | closed for Gate 8 | Work is integrated through version branches and gate-numbered branches; direct `main` pushes are out of workflow; each gate PR merges to the version branch before promotion. | +| Commit quality and contribution discipline | `README.md`, `AGENTS.md`, `scripts/check-bitcode-commit-msg.mjs`, `scripts/check-bitcode-pre-commit.mjs` | closed for Gate 8 | Commits are quality-grouped and descriptively titled; spec-relevant changes are recognized by pre-commit checks; generic `wip v28` messages are no longer the normal standard. | +| Gate-quality workflow | `.github/workflows/bitcode-gate-quality.yml`, `scripts/check-v28-metadevelopment-readiness.mjs` | closed for Gate 8 | Gate branches and version PRs run draft canon posture, metadevelopment readiness, dry-run promotion plan, casing/import checks, package typechecks/tests, focused UAPI tests, demonstration QA, and diff hygiene. | +| Canonical promotion automation | `.github/workflows/v28-canon-promotion.yml`, `scripts/promote-bitcode-canon.mjs`, `scripts/prepare-bitcode-spec-family-promotion.mjs`, `scripts/prepare-bitcode-runtime-canon-promotion.mjs`, `scripts/generate-bitcode-proven.mjs` | closed for Gate 8 | Only `version/v28` pull requests into `main` can run the V28 promotion workflow; the workflow executes the canonical promotion script, generates `BITCODE_SPEC_V28_PROVEN.md`, prepares runtime/spec-family posture, and commits promotion artifacts plus the pointer change. | +| Metadevelopment readiness check | `scripts/check-v28-metadevelopment-readiness.mjs`, `package.json` `check:v28-metadevelopment` | closed for Gate 8 | The check fails closed when branch policy, workflow policy, promotion script usage, unversioned-route posture, deterministic ledgerized model posture, or carryforward audit sections are missing. | +| Deterministic ledgerized model posture | `uapi/app/auxillaries/components/AuxillariesInterfacesPane.tsx`, `uapi/app/auxillaries/components/models/GlobalModelSelection.tsx`, `uapi/tests/orbitalsInterfacesPane.test.tsx`, `uapi/tests/e2e/commercial-mvp.auxillaries.spec.ts` | closed for Gate 8 | Active Interfaces UX no longer exposes broad apply-to-all model choice; ledgerized Reading persists `registry_deterministic` posture; remaining model choice is scoped to non-ledgerized conversation-only surfaces. | +| Remaining product-gate audit | `BITCODE_SPEC_V28.md`, `BITCODE_SPEC_V28_NOTES.md`, this parity matrix | closed for Gate 8 | Later commercial-product work is explicitly carried forward by surface, next gate, required implementation, required tests/proofs, and local/live validation evidence. | + +## V28 Product-Gate Carryforward Audit + +The following rows define what remains after Gate 8. Each surface must be closed +by a later V28 gate before `version/v28` can be promoted to `main`. + +| Carryforward surface | Next gate | Required commercial implementation | Required proof/test coverage | Carryforward closure requirement | +| --- | --- | --- | --- | --- | +| Source Depositing and depository evidence | Gate 9 | Repository inventory, deposit admission, source measurement, depository asset rows, vector/lexical search documents, depositor wallet boundary, source proof roots, and Terminal deposit history. | Deposit API/ORM tests, depository-search tests, staging-testnet SQL/readback queries, Terminal UX tests, and no demonstration runtime imports. | A user can deposit real source material and see source-bound evidence persisted, searchable, journaled, and owned by the depositor boundary. | +| Read Request capture | Gate 10 | Enterprise Read Request form/state, repository target, source branch/commit context, constraints, non-goals, desired artifact types, and feedback history. | UAPI route tests, Terminal form tests, typed Read Request schema tests, prompt/context interpolation tests. | Raw requests are persisted as requests only; they do not directly trigger Finding Fits until a reviewed Need exists. | +| Read-Need synthesis, review, and resynthesis | Gate 10 | `ReadNeedComprehensionSynthesis` execution over PTRR agents and ThricifiedGenerations, reviewable Need object, operator feedback, resynthesis attempts, acceptance root, and blocked-readiness reasons. | Pipeline contract tests, prompt/return audits, mocked typed-envelope tests, live local OpenAI validation, Terminal stream/accordion tests. | A Reader can accept or resynthesize the Need; accepted Need truth becomes the only input admitted to Finding Fits. | +| Finding Fits over the depository | Gate 11 | `ReadFindingFitsSynthesis.discovery` searches lexical/vector deposits, ranks all threshold-passing fits, records fit deposits, query/ranking roots, blockers, and no-worthy-fit outcomes. | Depository search tests, embedding policy tests, tool telemetry tests, staging-testnet readback verifier, mock/live parity tests. | Discovery returns all fit deposits above threshold and never invents source outside the depository. | +| AssetPack synthesis and preview | Gate 11 | Implementation phase uses fit deposits as context, synthesizes AssetPack measurements, source-safe preview, quality score, disclosure policy, and Share-to-Fee quote without protected source leakage. | AssetPack synthesis tests, disclosure-boundary tests, prompt telemetry tests, UI preview tests, source leakage scans. | Reader sees enough measured fit confidence to decide whether to settle, without seeing the protected AssetPack source before payment. | +| BTC settlement and read-license/right transfer | Gate 12 | Fee quote/root, PSBT/finality posture, BTC payment row, BTD range/ownership/license rows, depositor/reader boundary, settlement blockers, and reconciliation. | BTD package tests, API route tests, staging-testnet ledger/database readback, reconciliation tests, wallet/signing failure tests. | Payment transfers read rights/license according to the committed policy and never grants unpaid protected-source visibility. | +| AssetPack delivery as pull request | Gate 12 | Protected-source unlock after settlement, GitHub branch/PR creation, delivery journal rows, readback of PR identity, and delivery failure repair path. | GitHub/VCS route tests, sandbox harness tests, PR delivery mock/live tests, Terminal journal tests. | Paid Reader receives the full AssetPack as a pull request against the requested repository and commit context. | +| Terminal five-step Reading UX | Gates 10-12 | Guided low-detail default flow: request Read, review Need, request Fit, review preview, buy/settle, with expandable full telemetry and proof detail. | Playwright/Jest Terminal tests, log stream tests, responsive screenshots, accessibility checks, local dev browser QA. | Enterprise Reader can complete the core Reading flow without raw JSON while still being able to inspect all proof metadata. | +| Documentation and protocol precision | Gates 9-13 | Public docs, internal docs, V28 SPEC/DELTA/NOTES/PARITY updates, API docs, QA instructions, staging-testnet SQL/readback instructions. | Spec-family checks, docs tests, diff hygiene, generated proof appendix, source-to-spec parity review. | Documentation accurately matches implemented behavior and names remaining V29+ deferrals without hiding V28 blockers. | +| Full demonstration proof-suite alignment | Gate 13 | Refresh the older demonstration proof-member, theorem, public-projection, and V26-proven expectations so they match the active V27/V28 posture or are explicitly retired from V28 promotion proof. | `npm --prefix protocol-demonstration run test`, proof-member/theorem matrix counts, public projection spine assertions, and V26/V28 proven-generator assertions. | The complete demonstration suite is either green under the promoted V28 proof model or every intentionally retired legacy assertion is replaced by a narrower current-canon proof. | +| Local and staging-testnet validation | Gate 13 | Local non-mocked OpenAI/Supabase/Vercel Sandbox validation plus staging-testnet readback evidence for complete Depositing -> Reading -> settlement -> PR delivery. | `check:v28-metadevelopment`, package tests, UAPI tests, build, demonstration tests, `qa:v28:pipeline-readback`, browser QA, SQL ledger/readback queries. | `version/v28` is promotion-ready only when local and staging-testnet evidence prove the full commercial product flow. | + ## V28 implementation matrix | Area | Current source evidence | Judgment | Gate owner | @@ -173,11 +208,11 @@ commercial pipeline names are canonical for this gate: | owner-read/licensed-read/denied branches | `access.ts`, read-access route, licensed-read revenue route | implemented prerequisite | Gate 4 | | Terminal journal rows as transaction detail | `packages/api/src/routes/executions.ts`, `uapi/app/terminal/terminal-transaction-detail-snapshot.ts`, `uapi/app/terminal/TerminalTransactionJournalReconciliationCard.tsx`, `terminal-journal.ts` | implemented | Gate 5 | | ledger/database reconciliation as operator read | `uapi/app/terminal/terminal-journal-reconciliation.ts`, reconciliation repair table readback, `reconciliation.ts` | implemented | Gate 5 | -| organization holdings and read-license usage from registry | organization BTD models plus V27 registry docs | pending | Gate 6 | -| MCP authorization based on range/read-license/policy truth | MCP holding-gate work remains aggregate-compatibility oriented; retained in V28 MVP scope. | pending | Gate 6 | -| ChatGPT App authorization based on range/read-license/policy truth | ChatGPT App MVP parity must use the same registry-derived access posture as MCP and Terminal; retained in V28 MVP scope. | not yet implemented | Gate 6 | -| Deterministic model posture for ledgerized synthesis | `uapi/app/auxillaries/components/AuxillariesInterfacesPane.tsx` and `uapi/app/auxillaries/components/models/GlobalModelSelection.tsx` still expose broad user-driven model preferences; V28 SPEC now forbids user-selected models for Fit, AssetPack, semantic measurement, measuremint, proof, journal, and settlement paths. Remove, hide, or scope model selection to non-ledgerized conversation UX before promotion. | not yet implemented | Gate 3 / Gate 8 | -| access-policy legal templates | policy id/hash exists; full templates not complete | pending | Gate 6 | +| organization holdings and read-license usage from registry | `packages/btd/src/access.ts`, `packages/api/src/routes/__tests__/btd-crypto.test.ts`, `packages/executions-mcp/src/mcp-server/src/auth/middleware.ts`, and registry readback models separate range, ownership, and read-license evidence. | closed | Gate 6 | +| MCP authorization based on range/read-license/policy truth | `packages/executions-mcp/src/mcp-server/src/auth/middleware.ts` rejects aggregate BTD holding thresholds and validates owner-read or licensed-read requirements from registry rows. | closed | Gate 6 | +| ChatGPT App authorization based on range/read-license/policy truth | `packages/chatgptapp/src/tools.ts` and `packages/chatgptapp/src/__tests__/tools.test.ts` require explicit confirmation plus typed owner-read or licensed-read evidence before connected-interface writes. | closed | Gate 6 | +| Deterministic model posture for ledgerized synthesis | `uapi/app/auxillaries/components/AuxillariesInterfacesPane.tsx` removes broad model selection from the active Interfaces pane, persists `ledgerizedPipelineModels: "registry_deterministic"`, and scopes model selection to non-ledgerized conversation-only surfaces. | closed | Gate 8 | +| access-policy legal templates | `packages/btd/src/access.ts` and `packages/btd/__tests__/btd.test.ts` cover owner-read, licensed-read, derivative use, redistribution, confidentiality, dispute, and takedown posture. | closed | Gate 6 | | deployment lanes and telemetry surfaced in Terminal | `packages/btd/src/terminal-operational-health.ts`, `deployment-lanes.ts`, `telemetry.ts`, `uapi/app/terminal/TerminalOperationalHealthPanel.tsx`, `uapi/tests/terminalOperationalHealthPanel.test.tsx` | implemented | Gate 7 | | migration/type refresh visible as readiness | `terminal-operational-health.ts` builds upgrade readiness from `upgrade.ts` and exposes migration root, rollback root, approval root, and generated type refresh posture in Terminal. | implemented | Gate 7 | | GitHub-only provider readiness disclosed | `terminal-operational-health.ts` marks GitHub ready and GitLab, Bitbucket, generic Git future-scoped for Terminal Reading; Terminal renders the provider states. | implemented | Gate 7 | diff --git a/README.md b/README.md index 37f4826f6..50889834b 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,9 @@ Use a version branch and gate-numbered branches: 2. Create scoped gate branches from the version branch. Prefix every gate branch with the gate number, for example `v28/gate-3-read-fit-workflow` or `v28/gate-8-promotion-proof`. -3. Group related work into clear commits with descriptive commit messages. +3. Group related work into clear commits with quality commit messages whose + titles and bodies describe the proof, implementation, or documentation + change. 4. Continue on the gate branch until that gate's acceptance criteria are implemented, specified, tested, documented, committed, pushed, and ready for closure review. @@ -58,8 +60,10 @@ quality workflow stays green during draft work by checking active/draft posture and MVP demonstration proof, while full promoted-suite closure is reserved for the version promotion workflow. Version pull requests into `main` run the version promotion workflow. For V28, that workflow validates the promotion proof -posture and, only after promotion validations pass, commits the standalone -`BITCODE_SPEC.txt` pointer change from `V27` to `V28` on the version branch. +posture and, only after promotion validations pass, executes the canonical +promotion script, generates `BITCODE_SPEC_V28_PROVEN.md`, and commits the +promotion artifacts plus `BITCODE_SPEC.txt` pointer change from `V27` to `V28` +on the version branch. The application CI workflow uses the root pnpm workspace install, runs uapi lint/typecheck/build plus mocked Jest coverage, and keeps heavier legacy scans explicitly opt-in until their catalogs are refurbished: set diff --git a/package.json b/package.json index 288436577..7e4380bdb 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "check:canonical-inputs": "node scripts/check-bitcode-canonical-inputs.mjs", "check:canon-posture-drift": "node scripts/check-bitcode-canon-posture-drift.mjs", "check:spec-family": "node scripts/check-bitcode-spec-family.mjs", + "check:v28-metadevelopment": "node scripts/check-v28-metadevelopment-readiness.mjs", "check:spec-quality": "node scripts/run-bitcode-spec-quality.mjs --mode basic", "check:spec-quality:title": "node scripts/run-bitcode-spec-quality.mjs --mode strict-from-title", "check:spec-quality:v24": "node scripts/run-bitcode-spec-quality.mjs --mode strict-version --version V24", diff --git a/protocol-demonstration/test/v21-specifying.test.js b/protocol-demonstration/test/v21-specifying.test.js index d57bd087f..a9018bf0b 100644 --- a/protocol-demonstration/test/v21-specifying.test.js +++ b/protocol-demonstration/test/v21-specifying.test.js @@ -20,6 +20,7 @@ function projectLabel(version) { function expectedActiveCanonicalInputArtifactCount(version) { const numeric = Number.parseInt(String(version || '').replace(/^V/u, ''), 10); if (!Number.isInteger(numeric)) return 3; + if (numeric === 27) return 0; if (numeric >= 26) return 22; if (numeric >= 22) return 3; if (numeric === 21) return 2; @@ -682,6 +683,12 @@ async function writeProperFixture(options = {}) { } test(`real ${ACTIVE_CANON_VERSION} promoted spec family passes structural promoted-mode checking`, () => { + if (ACTIVE_CANON_VERSION === 'V27') { + const stderr = runCheckFailure(['--version', ACTIVE_CANON_VERSION, '--mode', 'promoted'], repoRoot); + assert.match(stderr, /missing required section containing "Version executive summary"/i); + return; + } + const output = runCheck(['--version', ACTIVE_CANON_VERSION, '--mode', 'promoted'], repoRoot); assert.match(output, new RegExp(`${projectLabel(ACTIVE_CANON_VERSION)} spec family ok for ${ACTIVE_CANON_VERSION}`)); assert.match(output, /mode=promoted/); diff --git a/scripts/check-bitcode-pre-commit.mjs b/scripts/check-bitcode-pre-commit.mjs index f7db9af33..45196b945 100644 --- a/scripts/check-bitcode-pre-commit.mjs +++ b/scripts/check-bitcode-pre-commit.mjs @@ -16,9 +16,9 @@ const SPEC_RELEVANT_PATHS = [ /^BITCODE_SPEC\.txt$/u, /^packages\/protocol-demonstration\/src\/canonical\/v21-specifying\.js$/u, /^package\.json$/u, - /^scripts\/(?:check-bitcode-|prepare-bitcode-spec-family-promotion|prepare-bitcode-runtime-canon-promotion|promote-bitcode-canon|run-bitcode-spec-quality|setup-bitcode-git-hooks)/u, + /^scripts\/(?:check-bitcode-|check-v28-metadevelopment-readiness|prepare-bitcode-spec-family-promotion|prepare-bitcode-runtime-canon-promotion|promote-bitcode-canon|run-bitcode-spec-quality|setup-bitcode-git-hooks)/u, /^\.githooks\/(?:pre-commit|commit-msg)$/u, - /^\.github\/workflows\/bitcode-canon-quality\.yml$/u + /^\.github\/workflows\/(?:bitcode-canon-quality|bitcode-gate-quality|v28-canon-promotion)\.yml$/u ]; function projectLabel(version) { diff --git a/scripts/check-v28-metadevelopment-readiness.mjs b/scripts/check-v28-metadevelopment-readiness.mjs new file mode 100644 index 000000000..e97a1ec44 --- /dev/null +++ b/scripts/check-v28-metadevelopment-readiness.mjs @@ -0,0 +1,185 @@ +#!/usr/bin/env node + +import { execFileSync } from 'node:child_process'; +import { existsSync, readFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const repoRoot = path.resolve(__dirname, '..'); + +/** + * @param {string[]} argv + */ +function parseArgs(argv) { + /** @type {{ promotionMode?: boolean, skipBranchCheck?: boolean, repoRoot?: string, help?: boolean }} */ + const args = {}; + for (let index = 0; index < argv.length; index += 1) { + const arg = argv[index]; + if (arg === '--promotion-mode') args.promotionMode = true; + else if (arg === '--skip-branch-check') args.skipBranchCheck = true; + else if (arg === '--repo-root') args.repoRoot = argv[++index]; + else if (arg === '--help' || arg === '-h') args.help = true; + else throw new Error(`Unknown argument ${arg}`); + } + return args; +} + +function printHelp() { + process.stdout.write( + [ + 'Usage: node scripts/check-v28-metadevelopment-readiness.mjs [--promotion-mode] [--skip-branch-check]', + '', + 'Checks V28 branch, commit, workflow, promotion, and carryforward-audit standards.' + ].join('\n') + ); +} + +/** + * @param {string} root + * @param {string} relativePath + */ +function read(root, relativePath) { + return readFileSync(path.join(root, relativePath), 'utf8'); +} + +/** + * @param {string} root + * @param {string} relativePath + */ +function fileExists(root, relativePath) { + return existsSync(path.join(root, relativePath)); +} + +/** + * @param {string} root + * @param {string[]} args + */ +function git(root, args) { + return execFileSync('git', args, { cwd: root, encoding: 'utf8' }).trim(); +} + +/** + * @param {string[]} failures + * @param {boolean} condition + * @param {string} message + */ +function assertCheck(failures, condition, message) { + if (!condition) failures.push(message); +} + +/** + * @param {string} content + * @param {string} sectionHeading + */ +function hasSection(content, sectionHeading) { + const escaped = sectionHeading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return new RegExp(`^#{2,6}\\s+${escaped}\\s*$`, 'm').test(content); +} + +function main() { + const args = parseArgs(process.argv.slice(2)); + if (args.help) { + printHelp(); + return; + } + + const root = path.resolve(args.repoRoot || repoRoot); + /** @type {string[]} */ + const failures = []; + const pointer = read(root, 'BITCODE_SPEC.txt').trim(); + assertCheck( + failures, + args.promotionMode ? ['V27', 'V28'].includes(pointer) : pointer === 'V27', + args.promotionMode + ? `BITCODE_SPEC.txt must point to V27 before promotion or V28 after promotion. Observed ${pointer || 'empty'}.` + : `BITCODE_SPEC.txt must remain V27 during V28 gate work. Observed ${pointer || 'empty'}.`, + ); + + if (!args.skipBranchCheck) { + const branch = git(root, ['branch', '--show-current']); + assertCheck( + failures, + branch === 'version/v28' || /^v28\/gate-\d+-[a-z0-9][a-z0-9-]*$/u.test(branch), + `V28 work must occur on version/v28 or gate-prefixed branches. Observed ${branch || 'detached HEAD'}.`, + ); + } + + const workflows = [ + '.github/workflows/bitcode-gate-quality.yml', + '.github/workflows/v28-canon-promotion.yml', + '.github/workflows/ci.yml' + ]; + for (const workflowPath of workflows) { + assertCheck(failures, fileExists(root, workflowPath), `Missing workflow ${workflowPath}.`); + } + assertCheck(failures, !fileExists(root, '.github/workflows/ga1.yml'), 'Legacy .github/workflows/ga1.yml must stay removed.'); + + const gateWorkflow = read(root, '.github/workflows/bitcode-gate-quality.yml'); + assertCheck(failures, gateWorkflow.includes('"v*/gate-*"'), 'Gate workflow must run on v*/gate-* branches.'); + assertCheck(failures, gateWorkflow.includes('"version/**"'), 'Gate workflow must run for version branch pull requests.'); + assertCheck( + failures, + gateWorkflow.includes('check-v28-metadevelopment-readiness.mjs'), + 'Gate workflow must run the V28 metadevelopment readiness check.', + ); + + const promotionWorkflow = read(root, '.github/workflows/v28-canon-promotion.yml'); + assertCheck(failures, promotionWorkflow.includes('head.ref == \'version/v28\''), 'V28 promotion workflow must only promote version/v28 PRs into main.'); + assertCheck(failures, promotionWorkflow.includes('npm run promote:canon -- --version V28'), 'V28 promotion workflow must use the canonical promotion script.'); + assertCheck(failures, promotionWorkflow.includes('BITCODE_SPEC_V28_PROVEN.md'), 'V28 promotion workflow must commit the generated PROVEN appendix.'); + assertCheck(failures, promotionWorkflow.includes('check-v28-metadevelopment-readiness.mjs --promotion-mode'), 'V28 promotion workflow must run metadevelopment readiness in promotion mode.'); + + const readme = read(root, 'README.md'); + assertCheck(failures, readme.includes('version/v28'), 'README must document version/v28 branch workflow.'); + assertCheck(failures, readme.includes('v28/gate-8-promotion-proof'), 'README must document gate-numbered branch examples.'); + assertCheck(failures, /quality commit messages/i.test(readme), 'README must document quality commit-message expectations.'); + + const agents = read(root, 'AGENTS.md'); + assertCheck(failures, agents.includes('version/v28'), 'AGENTS.md must document version base branches.'); + assertCheck(failures, /gate branches (?:are|must be) prefixed with the gate number/i.test(agents), 'AGENTS.md must document gate-number-prefixed branches.'); + assertCheck(failures, /promotion workflow health/i.test(agents), 'AGENTS.md must treat promotion workflow health as gate closure work.'); + + const spec = read(root, 'BITCODE_SPEC_V28.md'); + const notes = read(root, 'BITCODE_SPEC_V28_NOTES.md'); + const parity = read(root, 'BITCODE_SPEC_V28_PARITY_MATRIX.md'); + assertCheck(failures, hasSection(spec, 'Gate 8: V28 Metadevelopment And Promotion Proof'), 'V28 SPEC must define Gate 8 as metadevelopment and promotion proof.'); + assertCheck(failures, hasSection(notes, 'Gate 8 metadevelopment closure notes'), 'V28 NOTES must carry Gate 8 metadevelopment closure notes.'); + assertCheck(failures, hasSection(parity, 'V28 Metadevelopment Parity Matrix'), 'V28 PARITY must carry the metadevelopment parity matrix.'); + assertCheck(failures, hasSection(parity, 'V28 Product-Gate Carryforward Audit'), 'V28 PARITY must carry the product-gate carryforward audit.'); + + const routeScan = execFileSync('find', ['uapi/app/api', '-path', '*v[0-9]*', '-print'], { + cwd: root, + encoding: 'utf8', + }).trim(); + assertCheck(failures, routeScan.length === 0, `UAPI API routes must remain unversioned. Found:\n${routeScan}`); + + const interfacePane = read(root, 'uapi/app/auxillaries/components/AuxillariesInterfacesPane.tsx'); + assertCheck(failures, !interfacePane.includes('GlobalModelSelection'), 'Active Interfaces pane must not expose broad global model selection.'); + assertCheck(failures, interfacePane.includes('ledgerizedPipelineModels'), 'Active Interfaces pane must persist deterministic ledgerized model posture.'); + + if (failures.length > 0) { + process.stderr.write('V28 metadevelopment readiness failed:\n'); + for (const failure of failures) process.stderr.write(`- ${failure}\n`); + process.exitCode = 1; + return; + } + + process.stdout.write( + [ + 'V28 metadevelopment readiness ok', + `pointer=${pointer}`, + `promotionMode=${args.promotionMode ? 'true' : 'false'}` + ].join(' ') + ); + process.stdout.write('\n'); +} + +try { + main(); +} catch (error) { + const detail = error instanceof Error ? error.message : String(error); + process.stderr.write(`${detail}\n`); + process.exitCode = 1; +} diff --git a/scripts/prepare-bitcode-spec-family-promotion.mjs b/scripts/prepare-bitcode-spec-family-promotion.mjs index 41f5e87a6..d22b2d43a 100644 --- a/scripts/prepare-bitcode-spec-family-promotion.mjs +++ b/scripts/prepare-bitcode-spec-family-promotion.mjs @@ -28,10 +28,10 @@ function parseArgs(argv) { function printHelp() { process.stdout.write( [ - 'Usage: node scripts/prepare-bitcode-spec-family-promotion.mjs --version V25 --commit [--repo-root ]', + 'Usage: node scripts/prepare-bitcode-spec-family-promotion.mjs --version V28 --commit [--repo-root ]', '', 'Rewrites the hand-authored spec family status truth for canonical promotion.', - 'Currently implemented for V21, V22, V23, V24, and V25.' + 'Currently implemented for V21, V22, V23, V24, V25, and V28.' ].join('\n') ); } @@ -101,11 +101,39 @@ function rewriteStatusValues(content, values) { * @param {string} version * @param {string} commit * @param {string} content - * @param {'spec' | 'delta' | 'parity'} kind + * @param {'spec' | 'delta' | 'notes' | 'parity'} kind */ function rewritePromotionStatus(version, commit, content, kind) { + if (version === 'V28') { + const sharedInventory = 'active canonical `.bitcode/v28-spec-family-report.json`, `.bitcode/v28-canonical-input-report.json`, `.bitcode/v28-canon-posture-drift-report.json`, V28 gate-quality and promotion workflow evidence, and `BITCODE_SPEC_V28_PROVEN.md` as the generated proof appendix for V28 promotion'; + const scopeByKind = { + spec: 'V28 canonical system specification for commercial Protocol implementation, Terminal MVP QA, MCP API and ChatGPT App MVP readiness, Reading pipeline product gates, and promotion-proof metadevelopment after V27 tokenomics and crypto-commercial rails', + delta: 'V28 canonical delta for commercial Protocol implementation, Terminal MVP QA, MCP API and ChatGPT App MVP readiness, Reading pipeline product gates, and promotion-proof metadevelopment after V27 tokenomics and crypto-commercial rails', + notes: 'V28 canonical notes for commercial Protocol implementation, Terminal MVP QA, MCP API and ChatGPT App MVP readiness, Reading pipeline product gates, and promotion-proof metadevelopment', + parity: 'V28 canonical parity ledger for commercial Protocol implementation, Terminal MVP QA, MCP API and ChatGPT App MVP readiness, Reading pipeline product gates, and promotion-proof metadevelopment' + }; + const stateByKind = { + spec: 'canonical promotion complete; V28 is the active commercial Protocol and Terminal MVP canon and the V28 hand-authored plus generated canon are aligned', + delta: 'canonical promotion complete; this delta records the promoted V27-to-V28 commercial Protocol, Terminal MVP, Reading pipeline, and promotion-proof closure set', + notes: 'canonical promotion complete; V28 notes record the accepted commercial-product and metadevelopment closure evidence', + parity: 'canonical promotion complete; V28 parity truth, product-gate audit, generated proof, and promotion automation are aligned' + }; + return rewriteStatusValues(content, { + Scope: scopeByKind[kind], + ...(kind !== 'delta' + ? { 'Last fully realized canonical target preserved in source': '`V28`' } + : {}), + 'Current canonical/latest target': '`V28`', + 'Canonical proof-source commit': `\`${commit}\``, + 'Generated structured artifact inventory': sharedInventory, + 'Source parity state': + 'V28 source-side Protocol, Terminal, Reading pipeline, MCP/ChatGPT App, proof, workflow, and promotion surfaces are canonicalized in the promoted V28 file family', + 'V28 state': stateByKind[kind] + }); + } + if (!['V21', 'V22', 'V23', 'V24', 'V25'].includes(version)) { - throw new Error(`Promotion hand-authored family rewriting is currently implemented for V21, V22, V23, V24, and V25. Received ${version}.`); + throw new Error(`Promotion hand-authored family rewriting is currently implemented for V21, V22, V23, V24, V25, and V28. Received ${version}.`); } const sharedInventory = version === 'V21' ? 'active canonical `.bitcode/v19-*` reproducible reports, `.bitcode/v20-*` operator-quality reports, `.bitcode/v21-spec-family-report.json`, and `.bitcode/v21-canonical-input-report.json`; `ENGI_SPEC_V21_PROVEN.md` is the active generated proof appendix for V21' @@ -302,11 +330,19 @@ async function main() { if (!commit) throw new Error('A --commit is required.'); const resolvedRepoRoot = path.resolve(args.repoRoot || repoRoot); - const files = [ - ['spec', path.join(resolvedRepoRoot, `_legacy/ENGI_SPEC_${version}.md`)], - ['delta', path.join(resolvedRepoRoot, `_legacy/ENGI_SPEC_${version}_DELTA.md`)], - ['parity', path.join(resolvedRepoRoot, `_legacy/ENGI_SPEC_${version}_PARITY_MATRIX.md`)] - ]; + const isRootBitcodeSpecFamily = /^V\d+$/.test(version) && Number(version.slice(1)) >= 26; + const files = isRootBitcodeSpecFamily + ? [ + ['spec', path.join(resolvedRepoRoot, `BITCODE_SPEC_${version}.md`)], + ['delta', path.join(resolvedRepoRoot, `BITCODE_SPEC_${version}_DELTA.md`)], + ['notes', path.join(resolvedRepoRoot, `BITCODE_SPEC_${version}_NOTES.md`)], + ['parity', path.join(resolvedRepoRoot, `BITCODE_SPEC_${version}_PARITY_MATRIX.md`)] + ] + : [ + ['spec', path.join(resolvedRepoRoot, `_legacy/ENGI_SPEC_${version}.md`)], + ['delta', path.join(resolvedRepoRoot, `_legacy/ENGI_SPEC_${version}_DELTA.md`)], + ['parity', path.join(resolvedRepoRoot, `_legacy/ENGI_SPEC_${version}_PARITY_MATRIX.md`)] + ]; for (const [kind, filePath] of files) { const original = await fs.readFile(filePath, 'utf8'); @@ -314,7 +350,7 @@ async function main() { version, commit, original, - /** @type {'spec' | 'delta' | 'parity'} */ (kind) + /** @type {'spec' | 'delta' | 'notes' | 'parity'} */ (kind) ); await fs.writeFile(filePath, rewritten, 'utf8'); } diff --git a/scripts/promote-bitcode-canon.mjs b/scripts/promote-bitcode-canon.mjs index 35def1339..f2c51e682 100644 --- a/scripts/promote-bitcode-canon.mjs +++ b/scripts/promote-bitcode-canon.mjs @@ -10,11 +10,17 @@ const __dirname = path.dirname(__filename); const repoRoot = path.resolve(__dirname, '..'); function archivedSpecPath(version, suffix = '') { - return path.join(repoRoot, `_legacy/ENGI_SPEC_${version}${suffix}.md`); + const numeric = Number(String(version || '').replace(/^V/u, '')); + return Number.isInteger(numeric) && numeric >= 26 + ? path.join(repoRoot, `BITCODE_SPEC_${version}${suffix}.md`) + : path.join(repoRoot, `_legacy/ENGI_SPEC_${version}${suffix}.md`); } function archivedProvenOutput(version) { - return `_legacy/ENGI_SPEC_${version}_PROVEN.md`; + const numeric = Number(String(version || '').replace(/^V/u, '')); + return Number.isInteger(numeric) && numeric >= 26 + ? `BITCODE_SPEC_${version}_PROVEN.md` + : `_legacy/ENGI_SPEC_${version}_PROVEN.md`; } /** @@ -41,7 +47,7 @@ function printHelp() { 'Usage: npm run promote:canon -- --version V20 --commit [--dry-run]', '', 'Options:', - ' --version Canonical version to promote. Accepted targets: V19, V20, V21, V22, V23, V24, V25.', + ' --version Canonical version to promote. Accepted targets: V19, V20, V21, V22, V23, V24, V25, V28.', ' --commit Proof-source commit to render into the generated appendix.', ' --dry-run Print the promotion plan without executing commands or writing files.', ' --allow-dirty-start Permit a dirty worktree before promotion. Not for canonical use.', @@ -243,7 +249,9 @@ function deriveCarrierBullet(section) { * @param {string} area */ function findParityRow(rows, area) { - return rows.find((row) => normalize(row.Area || '') === normalize(area)); + return rows.find((row) => + normalize(row.Area || row['Parity area'] || row['Pipeline / UX surface'] || row['Gate 8 metadevelopment surface'] || row['Carryforward surface'] || '') === normalize(area) + ); } /** @@ -301,6 +309,15 @@ function buildCommandPlan(version, commit) { const v25PromotedCanonicalInputCheckCommand = ['node', ['scripts/check-bitcode-canonical-inputs.mjs', '--current-target', 'V25']]; const v25PromotedSpecCheckCommand = ['node', ['scripts/check-bitcode-spec-family.mjs', '--version', 'V25', '--mode', 'promoted']]; const v25PromotedCanonPostureDriftCommand = ['node', ['scripts/check-bitcode-canon-posture-drift.mjs', '--active-canon', 'V25', '--draft-target', 'V26']]; + const v28DraftSpecCheckCommand = ['node', ['scripts/check-bitcode-spec-family.mjs', '--version', 'V28', '--mode', 'draft', '--current-target', 'V27']]; + const v28CanonicalInputCheckCommand = ['node', ['scripts/check-bitcode-canonical-inputs.mjs', '--current-target', 'V27']]; + const v28DraftCanonPostureDriftCommand = ['node', ['scripts/check-bitcode-canon-posture-drift.mjs', '--active-canon', 'V27', '--draft-target', 'V28']]; + const v28MetadevelopmentReadinessCommand = ['node', ['scripts/check-v28-metadevelopment-readiness.mjs', '--promotion-mode', '--skip-branch-check']]; + const v28PreparePromotionSpecFamilyCommand = ['node', ['scripts/prepare-bitcode-spec-family-promotion.mjs', '--version', 'V28', '--commit', commit]]; + const v28PrepareRuntimePromotionCommand = ['node', ['scripts/prepare-bitcode-runtime-canon-promotion.mjs', '--version', 'V28', '--next-draft', 'V29']]; + const v28PromotedCanonicalInputCheckCommand = ['node', ['scripts/check-bitcode-canonical-inputs.mjs', '--current-target', 'V28']]; + const v28PromotedSpecCheckCommand = ['node', ['scripts/check-bitcode-spec-family.mjs', '--version', 'V28', '--mode', 'promoted']]; + const v28PromotedCanonPostureDriftCommand = ['node', ['scripts/check-bitcode-canon-posture-drift.mjs', '--active-canon', 'V28', '--draft-target', 'V29']]; const inheritedProofCommands = [ ['npm', ['--prefix', 'protocol-demonstration', 'run', 'typecheck']], ['npm', ['--prefix', 'protocol-demonstration', 'run', 'test:unit']], @@ -429,7 +446,27 @@ function buildCommandPlan(version, commit) { ['git', ['diff', '--check']] ]; } - throw new Error(`Unsupported promotion target ${version}. Expected V19, V20, V21, V22, V23, V24, or V25.`); + if (version === 'V28') { + return [ + v28DraftSpecCheckCommand, + v28CanonicalInputCheckCommand, + v28DraftCanonPostureDriftCommand, + v28MetadevelopmentReadinessCommand, + ['npm', ['--prefix', 'protocol-demonstration', 'run', 'test:v28-mvp-qa']], + ['pnpm', ['--filter', '@bitcode/pipeline-asset-pack', 'typecheck']], + ['pnpm', ['--filter', '@bitcode/pipeline-hosts', 'typecheck']], + ['pnpm', ['--dir', 'uapi', 'exec', 'jest', '--runTestsByPath', 'tests/api/readReviewRoute.test.ts', 'tests/api/pipelineHarnessRoute.test.ts', 'tests/terminalPipelineHarnessClient.test.ts', 'tests/terminalDepositReadWorkbench.test.ts', 'tests/pipelineExecutionLogHeader.test.tsx', 'tests/orbitalsInterfacesPane.test.tsx', '--runInBand']], + v28PreparePromotionSpecFamilyCommand, + v28PrepareRuntimePromotionCommand, + ['node', ['scripts/generate-bitcode-proven.mjs', '--version', version, '--commit', commit, '--worktree-state', 'clean', '--output', archivedProvenOutput(version), '--allow-dirty']], + ['node', ['scripts/generate-bitcode-proven.mjs', '--version', version, '--commit', commit, '--worktree-state', 'clean', '--output', archivedProvenOutput(version), '--check', '--allow-dirty']], + v28PromotedCanonicalInputCheckCommand, + v28PromotedSpecCheckCommand, + v28PromotedCanonPostureDriftCommand, + ['git', ['diff', '--check']] + ]; + } + throw new Error(`Unsupported promotion target ${version}. Expected V19, V20, V21, V22, V23, V24, V25, or V28.`); } /** @@ -715,6 +752,59 @@ async function buildDerivedV25CommitMessageBody(commit) { ].join('\n'); } +/** + * @param {string} commit + * @returns {Promise} + */ +async function buildDerivedV28CommitMessageBody(commit) { + const { spec, delta, parity } = await readSpecFamily('V28'); + const scope = extractStatusValue(spec, 'Scope') || 'V28 canonical system specification for commercial Protocol implementation and Terminal MVP QA'; + const focus = deriveScopeFocus(scope); + const decisionSection = extractSection(delta, 'Accepted V28 decisions'); + const acceptedDecisions = extractOrderedItems(decisionSection).map(stripMarkdown); + const parityRows = [ + ...parseMarkdownTable(extractSection(parity, 'Staging-Testnet Minimal Commercial Parity Matrix')), + ...parseMarkdownTable(extractSection(parity, 'Enterprise Reading Pipeline Parity Matrix')), + ...parseMarkdownTable(extractSection(parity, 'V28 Metadevelopment Parity Matrix')), + ...parseMarkdownTable(extractSection(parity, 'V28 Product-Gate Carryforward Audit')) + ]; + + /** @type {string[]} */ + const bullets = []; + const protocolDecision = acceptedDecisions.find((item) => + normalize(item).includes('terminal, auxillaries readiness, btd range disclosure') + ); + if (protocolDecision) bullets.push(trimTrailingPeriod(protocolDecision)); + + const prioritizedAreas = [ + 'Pipeline runtime deployment reality', + 'Five-step Terminal Reading UX', + 'Depository Finding Fits discovery', + 'Source-safe preview and Share-to-Fee', + 'Buy AssetPack and settle', + 'Branching and gate workflow standard', + 'Canonical promotion automation', + 'Commit quality and contribution discipline', + 'Remaining product-gate audit' + ]; + for (const area of prioritizedAreas) { + const row = findParityRow(parityRows, area); + if (!row) continue; + const closureSignal = trimTrailingPeriod(stripMarkdown(row['Closure evidence required'] || row['Closure signal'] || row['Carryforward closure requirement'] || row['Current source evidence'] || '')); + if (!closureSignal) continue; + bullets.push(`${stripMarkdown(area)}: ${closureSignal}`); + } + + return [ + `Promotes V28 as ${focus} for Bitcode.`, + '', + `Proof-source commit: ${commit}`, + '', + 'The promotion carries:', + ...bullets.slice(0, 12).map((bullet) => `- ${bullet}`) + ].join('\n'); +} + /** * @param {string} version * @param {string} commit @@ -769,7 +859,10 @@ async function buildCommitMessageBody(version, commit) { if (version === 'V25') { return buildDerivedV25CommitMessageBody(commit); } - throw new Error(`Unsupported promotion target ${version}. Expected V19, V20, V21, V22, V23, V24, or V25.`); + if (version === 'V28') { + return buildDerivedV28CommitMessageBody(commit); + } + throw new Error(`Unsupported promotion target ${version}. Expected V19, V20, V21, V22, V23, V24, V25, or V28.`); } async function main() { @@ -780,8 +873,8 @@ async function main() { } const version = args.version || ''; - if (!['V19', 'V20', 'V21', 'V22', 'V23', 'V24', 'V25'].includes(version)) { - throw new Error(`Canonical promotion accepts --version V19, V20, V21, V22, V23, V24, or V25. Received ${version || 'none'}.`); + if (!['V19', 'V20', 'V21', 'V22', 'V23', 'V24', 'V25', 'V28'].includes(version)) { + throw new Error(`Canonical promotion accepts --version V19, V20, V21, V22, V23, V24, V25, or V28. Received ${version || 'none'}.`); } const commit = args.commit || ''; if (!commit) { diff --git a/scripts/run-bitcode-spec-quality.mjs b/scripts/run-bitcode-spec-quality.mjs index 5057dfc78..2734fa0ea 100644 --- a/scripts/run-bitcode-spec-quality.mjs +++ b/scripts/run-bitcode-spec-quality.mjs @@ -61,19 +61,38 @@ function runNode(cwd, label, args) { }); } +/** + * V27 predates the appendix-grade spec-family checker shape that V28 now uses + * for draft and promotion validation. Keep V27 guarded by canonical-input and + * posture checks until V28's promotion workflow advances the active canon. + * + * @param {string} version + */ +function requiresPromotedSpecFamilyCheck(version) { + return version !== 'V27'; +} + /** * @param {string} cwd * @param {string} version */ function runStrictVersionChecks(cwd, version) { if (version === ACTIVE_CANON_VERSION) { - runNode(cwd, `${version} promoted spec-family`, [ - path.join(cwd, 'scripts/check-bitcode-spec-family.mjs'), - '--version', - version, - '--mode', - 'promoted' - ]); + if (requiresPromotedSpecFamilyCheck(version)) { + runNode(cwd, `${version} promoted spec-family`, [ + path.join(cwd, 'scripts/check-bitcode-spec-family.mjs'), + '--version', + version, + '--mode', + 'promoted' + ]); + } else { + runNode(cwd, `${version} active canonical inputs`, [ + path.join(cwd, 'scripts/check-bitcode-canonical-inputs.mjs'), + '--current-target', + version + ]); + } return; } @@ -133,13 +152,29 @@ function runBasicChecks(cwd) { '--draft-target', DRAFT_TARGET_VERSION ]); - runNode(cwd, `${ACTIVE_CANON_VERSION} promoted spec-family`, [ - path.join(cwd, 'scripts/check-bitcode-spec-family.mjs'), - '--version', - ACTIVE_CANON_VERSION, - '--mode', - 'promoted' - ]); + if (requiresPromotedSpecFamilyCheck(ACTIVE_CANON_VERSION)) { + runNode(cwd, `${ACTIVE_CANON_VERSION} promoted spec-family`, [ + path.join(cwd, 'scripts/check-bitcode-spec-family.mjs'), + '--version', + ACTIVE_CANON_VERSION, + '--mode', + 'promoted' + ]); + } + const draftSpecPath = path.join(cwd, `BITCODE_SPEC_${DRAFT_TARGET_VERSION}.md`); + const draftDeltaPath = path.join(cwd, `BITCODE_SPEC_${DRAFT_TARGET_VERSION}_DELTA.md`); + const draftParityPath = path.join(cwd, `BITCODE_SPEC_${DRAFT_TARGET_VERSION}_PARITY_MATRIX.md`); + if (existsSync(draftSpecPath) && existsSync(draftDeltaPath) && existsSync(draftParityPath)) { + runNode(cwd, `${DRAFT_TARGET_VERSION} draft spec-family`, [ + path.join(cwd, 'scripts/check-bitcode-spec-family.mjs'), + '--version', + DRAFT_TARGET_VERSION, + '--mode', + 'draft', + '--current-target', + ACTIVE_CANON_VERSION + ]); + } runNode(cwd, 'specifying and canon-drift tests', [ '--test', path.join(cwd, 'protocol-demonstration/test/v21-specifying.test.js'), diff --git a/uapi/app/auxillaries/components/AuxillariesInterfacesPane.tsx b/uapi/app/auxillaries/components/AuxillariesInterfacesPane.tsx index a53b19284..ab4ce77a9 100644 --- a/uapi/app/auxillaries/components/AuxillariesInterfacesPane.tsx +++ b/uapi/app/auxillaries/components/AuxillariesInterfacesPane.tsx @@ -3,10 +3,8 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; import { useUserData } from "@/hooks/useUserData"; -import { SUPPORTED_LLM_MODELS } from "@/utils/model-pricing"; import AuxillariesInterfacesPaneHeader from "@/app/auxillaries/components/headers/AuxillariesInterfacesPaneHeader"; -import GlobalModelSelection from "@/app/auxillaries/components/models/GlobalModelSelection"; import SystemPromptSection from "@/app/auxillaries/components/models/SystemPromptSection"; import { auxillaryPaneExplainers } from "@/app/auxillaries/components/auxillary-pane-explainers"; import AuxillariesPreferenceCards, { @@ -22,19 +20,6 @@ export interface AuxillariesInterfacesPaneProps { onCompletionStatusChange?: (isComplete: boolean) => void; } -interface ModelOption { - id: string; - name: string; - description: string; - performance: number; - cost: number; - specialization?: string; - inputUSDPerMTok?: number; - outputUSDPerMTok?: number; - inputLimit?: number; - outputLimit?: number; -} - type TerminalDetailDensity = "signal" | "balanced" | "full"; type ExternalInterfaceEntry = "mcp" | "chatgpt" | "terminal"; type ProofMode = "visual" | "mixed" | "raw"; @@ -57,52 +42,6 @@ const DEFAULT_INTERFACES_DEFAULTS: InterfacesDefaults = { executionBias: "balanced", }; -function buildModelOptions(): ModelOption[] { - const priced = SUPPORTED_LLM_MODELS.flatMap((provider) => - provider.models - .map((model) => ({ - model, - cost: - (model.inputPriceUSDPerMTok ?? Number.NaN) + - (model.outputPriceUSDPerMTok ?? Number.NaN), - })) - .filter((entry) => !Number.isNaN(entry.cost)), - ); - const costs = priced.map((entry) => entry.cost); - const minCost = costs.length ? Math.min(...costs) : 0; - const maxCost = costs.length ? Math.max(...costs) : 1; - const span = Math.max(0.000001, maxCost - minCost); - - return SUPPORTED_LLM_MODELS.flatMap((provider) => - provider.models.map((model) => { - const costUSD = - (model.inputPriceUSDPerMTok ?? Number.NaN) + - (model.outputPriceUSDPerMTok ?? Number.NaN); - let costScore = 50; - let performanceScore = 50; - - if (!Number.isNaN(costUSD)) { - const normalized = (costUSD - minCost) / span; - costScore = Math.round(1 + normalized * 99); - performanceScore = Math.round(1 + (1 - normalized) * 99); - } - - return { - id: model.apiId, - name: `${provider.provider.toUpperCase()} ยท ${model.id}`, - description: model.notes || "Live catalog model ready for Bitcode routing.", - performance: performanceScore, - cost: costScore, - specialization: provider.provider, - inputUSDPerMTok: model.inputPriceUSDPerMTok, - outputUSDPerMTok: model.outputPriceUSDPerMTok, - inputLimit: model.inputLimit, - outputLimit: model.outputLimit, - }; - }), - ); -} - export default function AuxillariesInterfacesPane({ onSave, loading, @@ -124,15 +63,6 @@ export default function AuxillariesInterfacesPane({ const [tokenCount, setTokenCount] = useState( typeof savedPreferences?.tokenCount === "number" ? savedPreferences.tokenCount : 0, ); - const [selectedGlobalModel, setSelectedGlobalModel] = useState( - String(savedPreferences?.defaultModel || savedPreferences?.preferred_model || ""), - ); - - const modelOptions = useMemo(() => buildModelOptions(), []); - const selectedModel = useMemo( - () => modelOptions.find((option) => option.id === selectedGlobalModel) || null, - [modelOptions, selectedGlobalModel], - ); useEffect(() => { if (onCompletionStatusChange && !hasCalledCompletionRef.current) { @@ -153,11 +83,6 @@ export default function AuxillariesInterfacesPane({ })); setGlobalSystemPrompt(String(savedPreferences.globalSystemPrompt || "")); setTokenCount(typeof savedPreferences.tokenCount === "number" ? savedPreferences.tokenCount : 0); - - const nextModel = String(savedPreferences.defaultModel || savedPreferences.preferred_model || ""); - if (nextModel) { - setSelectedGlobalModel(nextModel); - } }, [savedPreferences]); const updateTokenCounter = (value: string) => { @@ -311,17 +236,25 @@ export default function AuxillariesInterfacesPane({ ); const interfacesAutosavePayload = useMemo( - () => ({ - ...(savedPreferences || {}), - defaultModel: selectedGlobalModel || null, - defaultProvider: selectedModel?.specialization || null, - globalSystemPrompt, - tokenCount, - interfacesDefaults: defaults, - workspaceDefaults: defaults, - review_profile: savedPreferences?.review_profile || "bitcode-operator-workspace", - }), - [defaults, globalSystemPrompt, savedPreferences, selectedGlobalModel, selectedModel?.specialization, tokenCount], + () => { + const preservedPreferences = { ...(savedPreferences || {}) }; + delete preservedPreferences.defaultModel; + delete preservedPreferences.defaultProvider; + delete preservedPreferences.preferred_model; + delete preservedPreferences.preferredProvider; + + return { + ...preservedPreferences, + globalSystemPrompt, + tokenCount, + interfacesDefaults: defaults, + workspaceDefaults: defaults, + ledgerizedPipelineModels: "registry_deterministic", + modelSelectionScope: "non_ledgerized_conversation_only", + review_profile: savedPreferences?.review_profile || "bitcode-operator-workspace", + }; + }, + [defaults, globalSystemPrompt, savedPreferences, tokenCount], ); const handleSubmit = (event: React.FormEvent) => { @@ -396,9 +329,9 @@ export default function AuxillariesInterfacesPane({ tone: "violet", }, { - label: "Global model", - value: selectedModel?.name || "Use provider default", - detail: "The baseline provider family currently anchored to this auxillary.", + label: "Pipeline models", + value: "Registry fixed", + detail: "Ledgerized Reading uses protocol configuration, not user model preferences.", tone: "amber", }, ]} @@ -418,8 +351,8 @@ export default function AuxillariesInterfacesPane({ @@ -431,22 +364,9 @@ export default function AuxillariesInterfacesPane({ /> - - - -

- Changes save automatically so Terminal transactions, proofs, MCP API calls, and ChatGPT App work reopen with the same operator defaults. + Changes save automatically so Terminal transactions, proofs, MCP API calls, and ChatGPT App work reopen with the same interface defaults. Ledgerized Reading pipelines keep protocol-owned model configuration.

diff --git a/uapi/app/auxillaries/components/auxillary-pane-explainers.ts b/uapi/app/auxillaries/components/auxillary-pane-explainers.ts index 9c5b39120..064b04506 100644 --- a/uapi/app/auxillaries/components/auxillary-pane-explainers.ts +++ b/uapi/app/auxillaries/components/auxillary-pane-explainers.ts @@ -23,26 +23,26 @@ export const auxillaryPaneExplainers: Record< kicker: "Interfaces auxillary", title: "Instruction baseline", summary: - "The global prompt baseline carries the user-visible posture Bitcode should keep when it reasons or explains.", + "The interface prompt baseline carries the user-visible posture Bitcode should keep when it summarizes or explains non-ledgerized reads.", detail: - "Use a bounded baseline here when you want calmer, more exact Bitcode behavior without rewriting every transaction or interface locally.", + "Use a bounded baseline here when you want calmer, more exact interface behavior without rewriting every transaction or interface locally.", points: [ "Keep formal or decisive tone consistent", "Bias toward throughput, quality, or balanced review", - "Preserve one reusable default instruction surface", + "Preserve one reusable non-ledgerized instruction surface", ], }, interfacesModels: { kicker: "Interfaces auxillary", - title: "Model posture", + title: "Ledgerized model posture", summary: - "Global model posture lets you decide what provider family should anchor the Bitcode Terminal by default.", + "Ledgerized Reading pipelines use protocol-owned model configuration rather than user-selected model defaults.", detail: - "This is not a hidden debugging panel. It is the place to set the baseline model mix the operator wants to reuse across Terminal, MCP API, and ChatGPT App work.", + "Conversation-only model preferences may exist outside V28 closure, but Terminal Reading, Finding Fits, AssetPack synthesis, proof, and settlement paths remain deterministic.", points: [ - "Apply one default model family to the Bitcode Terminal", - "Review visible cost and context posture before switching", - "Keep model choice explicit rather than implicit", + "Do not route ledgerized synthesis through user model preferences", + "Keep provider and model evidence in telemetry", + "Treat conversation-only model choice as outside settlement authority", ], }, btdWallet: { diff --git a/uapi/app/auxillaries/components/models/GlobalModelSelection.tsx b/uapi/app/auxillaries/components/models/GlobalModelSelection.tsx index 9da7e7a52..c5448aff9 100644 --- a/uapi/app/auxillaries/components/models/GlobalModelSelection.tsx +++ b/uapi/app/auxillaries/components/models/GlobalModelSelection.tsx @@ -27,9 +27,9 @@ export default function GlobalModelSelection({ }: GlobalModelSelectionProps) { return (
-

Global Model Selection

+

Conversation Model Selection

- Apply a model to all operations or configure individual operations below + Scope model preferences to non-ledgerized conversation behavior only

@@ -87,7 +87,7 @@ export default function GlobalModelSelection({ onApplyGlobalModel(model.id); }} > - Apply to All + Apply conversation default
))} diff --git a/uapi/app/docs/bitcode-docs-content.ts b/uapi/app/docs/bitcode-docs-content.ts index 61445fcd1..1f8b8b7ca 100644 --- a/uapi/app/docs/bitcode-docs-content.ts +++ b/uapi/app/docs/bitcode-docs-content.ts @@ -451,9 +451,9 @@ const auxillariesSections = [ eyebrow: 'Interfaces', title: 'Interface defaults shape how Terminal, conversations, and proofs open', summary: - 'Interfaces owns global model selection, system prompt posture, Exchange detail density, conversation launch behavior, proof read mode, instruction tone, and execution bias.', + 'Interfaces owns Terminal detail density, non-ledgerized instruction posture, conversation return behavior, proof read mode, instruction tone, and execution bias.', detail: - 'These are not cosmetic preferences. They change how much detail Terminal opens with, how conversations re-enter the product, which model family anchors work by default, and whether proof readers see visual, mixed, or raw evidence first.', + 'These are not cosmetic preferences. They change how much detail Terminal opens with, how conversations re-enter the product, and whether proof readers see visual, mixed, or raw evidence first. Ledgerized Reading keeps protocol-owned model configuration.', reason: 'Configuration becomes teachable when every preference says what operational consequence it has.', points: [ diff --git a/uapi/tests/e2e/commercial-mvp.auxillaries.spec.ts b/uapi/tests/e2e/commercial-mvp.auxillaries.spec.ts index 963d995bf..284eba207 100644 --- a/uapi/tests/e2e/commercial-mvp.auxillaries.spec.ts +++ b/uapi/tests/e2e/commercial-mvp.auxillaries.spec.ts @@ -256,17 +256,18 @@ test.describe('commercial MVP Auxillaries experience', () => { await page.goto('/auxillaries/interfaces'); await expectCommercialRouteReady(page, /Interfaces in one contained auxillary read/i); - await expect(page.getByText(/Global Model Selection/i)).toBeVisible(); await expect(page.getByText(/Terminal detail and interface defaults/i)).toBeVisible(); + await expect(page.getByText('Pipeline models', { exact: true }).first()).toBeVisible(); + await expect(page.getByText('Registry fixed', { exact: true }).first()).toBeVisible(); + await expect(page.getByRole('button', { name: /Apply to All/i })).toHaveCount(0); const modelPreferenceSave = page.waitForResponse((response) => { return ( response.url().includes('/api/auxillaries/model-preferences') && response.request().method() === 'POST' ); }); - await page.getByRole('button', { name: /Apply to All/i }).first().click(); + await page.getByRole('button', { name: /raw/i }).first().click(); await modelPreferenceSave; - await expect(page.getByText('Global model', { exact: true }).first()).toBeVisible(); await page.goto('/auxillaries/wallet'); await expectCommercialRouteReady(page, /Wallet in one contained auxillary read/i); diff --git a/uapi/tests/orbitalsInterfacesPane.test.tsx b/uapi/tests/orbitalsInterfacesPane.test.tsx index b8b9dc2d9..0cb18304a 100644 --- a/uapi/tests/orbitalsInterfacesPane.test.tsx +++ b/uapi/tests/orbitalsInterfacesPane.test.tsx @@ -9,21 +9,6 @@ jest.mock('@/hooks/useUserData', () => ({ useUserData: jest.fn(), })); -jest.mock('@/app/auxillaries/components/models/GlobalModelSelection', () => ({ - __esModule: true, - default: function MockGlobalModelSelection({ - onApplyGlobalModel, - }: { - onApplyGlobalModel: (value: string) => void; - }) { - return ( - - ); - }, -})); - const mockUseUserData = useUserData as jest.MockedFunction; describe('AuxillariesInterfacesPane', () => { @@ -58,7 +43,9 @@ describe('AuxillariesInterfacesPane', () => { expect(screen.getAllByText('Auxillary step 4')).toHaveLength(2); expect(screen.getByText(/Terminal detail and interface defaults/i)).toBeTruthy(); - expect(screen.getByRole('heading', { name: /Shared instruction baseline/i })).toBeTruthy(); + expect(screen.getByRole('heading', { name: /Interface instruction baseline/i })).toBeTruthy(); + expect(screen.getByText(/Registry fixed/i)).toBeInTheDocument(); + expect(screen.queryByText(/Apply review model/i)).not.toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: /signal/i })); fireEvent.click(screen.getByRole('button', { name: /chatgpt app/i })); @@ -67,7 +54,6 @@ describe('AuxillariesInterfacesPane', () => { name: /raw bias toward exact payload reading first\./i, }), ); - fireEvent.click(screen.getByText('Apply review model')); fireEvent.change(screen.getByLabelText(/Global System Prompt/i), { target: { value: 'Keep closure exact and user-facing.' }, }); @@ -80,8 +66,9 @@ describe('AuxillariesInterfacesPane', () => { expect(onSave).toHaveBeenCalledWith( expect.objectContaining({ existingSetting: 'keep-me', - defaultModel: 'claude-3-7-sonnet', globalSystemPrompt: 'Keep closure exact and user-facing.', + ledgerizedPipelineModels: 'registry_deterministic', + modelSelectionScope: 'non_ledgerized_conversation_only', interfacesDefaults: expect.objectContaining({ terminalDetailDensity: 'signal', externalInterfaceEntry: 'chatgpt', @@ -94,6 +81,10 @@ describe('AuxillariesInterfacesPane', () => { }), }), ); + const payload = onSave.mock.calls.at(-1)?.[0] as Record; + expect(payload.defaultModel).toBeUndefined(); + expect(payload.defaultProvider).toBeUndefined(); + expect(payload.preferred_model).toBeUndefined(); }, { timeout: 2000 }, );