diff --git a/BITCODE_SPEC_V28.md b/BITCODE_SPEC_V28.md index 0b89f5dab..c3846cab0 100644 --- a/BITCODE_SPEC_V28.md +++ b/BITCODE_SPEC_V28.md @@ -360,19 +360,19 @@ Acceptance criteria: with repository, branch, commit, signer, and wallet authorization posture; - the Read and Fit rows must carry the same repository, branch, and commit as the deposited source revision; -- candidate recall must prefer source and proof surfaces that explain the +- Finding Fits discovery must prefer source and proof surfaces that explain the product-critical path, including Terminal Deposit/Read components, execution history persistence, repository source selection, wallet/GitHub readiness, QA SQL, and BTD/ledger proof readback; - depository search must be a reusable AssetPack primitive, not a UI-only posture: every pipeline run stores searched asset count, query root, ranking - root, selected candidate ids, rejected/blocker reasons, proof/measurement + root, fit deposit ids, rejected/blocker reasons, proof/measurement posture, embedding policy, and result state under the execution evidence tree; - risk-admission and readiness agents have an evidence-only verification tool, `bitcode.asset-pack.verification`, that reads back the current depository search result or reruns search from pipeline input, then returns bounded - proof, measurement, readback, source-binding, selected-candidate, blocker, + proof, measurement, readback, source-binding, fit-deposit, blocker, warning, query-root, ranking-root, and embedding-policy evidence. The tool must not mutate state, deliver assets, mint, settle, or expose private AssetPack source before settlement; @@ -418,7 +418,7 @@ Acceptance criteria: V28 now admits a lightweight deployment harness because the first value-bearing Read/Fit result cannot be reviewed from local posture rows alone. The harness is not the full V34 distributed deployment program; it is the -minimum real host contract needed to prove that AssetPack pipeline execution, +minimum real host contract needed to prove that AssetPack pipeline run, agent/prompt/tool context, artifact export, and telemetry readback are running before Terminal may classify a Read as `worthy_fit` or `no_worthy_fit`. @@ -445,12 +445,12 @@ The V28 pipeline harness must write a manifest before execution containing: and asset measurement posture but has not stored root fields, the harness must materialize deterministic manifest-bound roots from the Deposit id, AssetPack id, Read id, repository, branch, and commit. These roots are - admissible as harness evidence roots for candidate recall and Fit review; + admissible as harness evidence roots for Finding Fits discovery and Fit review; they are not external finality claims and still require SQL/ledger readback before settlement. - repository full name, branch, and commit; - host capability summary; -- expected stages: deposit search, candidate ranking, Read comprehension, +- expected stages: deposit search, fit deposit ranking, Read comprehension, AssetPack synthesis, validation, Finish, and telemetry readback; - expected SQL evidence surfaces: `executions`, `execution_events`, `pipeline_runs`, `run_jobs`, `stream_logs`, `phase_executions`, @@ -458,7 +458,7 @@ The V28 pipeline harness must write a manifest before execution containing: `deliverable_pipeline_phase_delegations`, `deliverable_pipeline_agent_steps`, `deliverable_pipeline_generations`, and `deliverable_pipeline_tool_executions`; - PTRR agent executions must resolve the evidence tools registered by their - parent pipeline execution. A requested evidence tool must either execute and + parent pipeline run. A requested evidence tool must either execute and persist tool telemetry or fail as typed blocked-readiness evidence; missing registry linkage is not admissible live-pipeline behavior. Artifact stream events must carry the tool name, input/output/error presence, and ok/error @@ -509,7 +509,7 @@ Read/Fit result review remains fail-closed: pushes finished state to a server-side stream/socket handler. A run with omitted per-agent `*_USE_PTRR` flags is not an acceptable staging posture unless the global real-inference flag and explicit profile are present. -- manifest-only Deposit supply can satisfy candidate recall but cannot produce +- manifest-only Deposit supply can satisfy Finding Fits discovery but cannot produce `worthy_fit` unless proof and measurement posture are explicitly visible to the pipeline input and the downstream readback queries confirm them. Boolean wallet/measurement posture must be converted into deterministic @@ -517,7 +517,7 @@ Read/Fit result review remains fail-closed: evaluation; if that conversion cannot be made, the run must return explicit `blocked_readiness`. Manifest-bound roots support Read/Fit selection but do not claim BTC fee broadcast, BTD minting, or external ledger finality. -- every SDIVF phase, PTRR agent step, ThriceifiedGeneration, and tool execution +- every SDIVF phase, PTRR agent step, ThricifiedGeneration, and tool execution must be inspectable as prompt/context input, raw model/tool output, parsed/typed output, usage/timing metadata, and phase/agent/step/failsafe correlation. A harness failure must still export the execution tree so @@ -592,36 +592,70 @@ Read/Fit result review remains fail-closed: cannot close a live source-revision settlement/finality gate unless the same source and database/ledger rows are read back from the accepted environment. -### Gate 4: Staged Reading, AssetPack Preview, And Settlement UX +### Gate 4: Enterprise Reading Pipelines And Settlement UX Purpose: -Finish the two-stage Reading product experience: synthesize a Read-Need from a -reader request, let the reader review that Need, run Need-Fit search and -AssetPack synthesis from the accepted Need, preview the fit without leaking -protected source, settle the deterministic BTC fee, and deliver the purchased -AssetPack as a pull request. +Finish the enterprise Reading product experience around two named pipelines +and five reviewable Terminal steps. `ReadNeedComprehensionSynthesis` turns a +reader's Read Request into a measured Need that the reader can review. +`ReadFindingFitsSynthesis` accepts only that reviewed Need, searches the Bitcode +Depository for every qualifying fit deposit above the configured confidence and +quality thresholds, synthesizes a source-safe AssetPack from those deposits +when enough fit evidence exists, and prepares BTC settlement and pull-request +delivery evidence. Acceptance criteria: -- Terminal separates Read Request, Read-Need review, Need-Fit search, - AssetPack preview, BTC settlement, unlock, and pull-request delivery into - navigable user-visible stages. -- the Read-Need synthesis pipeline stores prompt inputs, interpolated context, - model outputs, parsed Need, measurement root, review state, feedback history, - and resynthesis attempts. -- the Need-Fit pipeline accepts only reviewed Read-Needs, searches deposited - supply, ranks source-bound candidates, synthesizes the AssetPack, and emits - fit/no-fit/blocked evidence with candidate, score, proof, and rejection - telemetry. -- AssetPack preview exposes measurements, score posture, fit rationale, roots, - fee quote, range projection, and disclosure policy without exposing protected - source before settlement. +- Terminal separates exactly five enterprise Reading user steps: request Read, + review synthesized Need, request Fit, review synthesized AssetPack, and buy + AssetPack/settle. +- `ReadNeedComprehensionSynthesis` owns request normalization, Need + comprehension, Need measurement, and operator review. All phase ids, agent + ids, PTRR step ids, ThricifiedGeneration ids, prompt ids, and telemetry ids are prefixed under + `ReadNeedComprehensionSynthesis`. +- `ReadFindingFitsSynthesis` owns accepted-Need admission, setup/read + comprehension, depository discovery, vector/lexical Finding Fits search, + AssetPack implementation from qualifying fit deposits, validation, + source-safe preview, Share-to-Fee pricing, settlement readback, and + pull-request delivery. All phase ids, agent ids, PTRR step ids, + ThricifiedGeneration ids, tool ids, prompt ids, and telemetry ids are prefixed under + `ReadFindingFitsSynthesis`. +- the Read-Need comprehension pipeline stores prompt templates, prompt inputs, + interpolated context, raw model responses when used, parsed typed Need, + measurement root, review state, feedback history, resynthesis attempts, and + acceptance roots. +- Read-Need synthesis tests cover typed Need construction, measurement roots, + source constraints, pricing measurement vectors, review-state transitions, + feedback/resynthesis telemetry, and route responses that expose prompt input, + prompt templates, interpolated context, raw output posture, parsed typed + output, and next action. +- the Finding Fits pipeline accepts only reviewed Read-Needs, searches + deposited supply, ranks source-bound deposit fits, admits every qualifying + fit deposit above the configured thresholds, synthesizes the AssetPack from + those fit deposits, and emits fit/no-fit/blocked evidence with fit deposit + ids, score, proof, measurement, readback, and rejection telemetry. +- Finding Fits tests cover strict accepted-Need admission, depository search, + vector/embedding search RPC shape, fit deposit ranking, source-bound proof + and measurement blockers, `worthy_fit`, `no_worthy_fit`, and + `blocked_readiness` branches, source-safe preview, Share-to-Fee quote, range + projection, settlement boundary, and pull-request delivery target posture. +- Every Reading pipeline phase must declare its phase, agent, PTRR step, + ThricifiedGeneration ids, prompt template, interpolated prompt/context input, + tool input, tool output, raw model response, parsed typed output, + timing/usage, and fail-closed state in telemetry. Until a phase is fully + live, tests must require valid typed mocks for the same + phase/agent/tool/PTRR/ThricifiedGeneration envelopes so mock mode cannot + drift from the live contract. +- AssetPack preview exposes measurements, score posture, fit rationale, fit + deposit ids, roots, fee quote, range projection, and disclosure policy + without exposing protected source or source-bearing patches before + settlement. - Share-to-Fee calculation is implemented as an auditable structured policy: measurement weights, measurement volumes, admitted fit quality, floor/dust rules, BTC network fee posture, and final quote roots are stored and tested. - settlement requires reader BTC payment authorization and readback of fee, - BTD ownership/license, journal, anchor, range, and database projection rows - before unlock. + BTD ownership/license transfer to the paying reader, journal, anchor, range, + and database projection rows before unlock. - Terminal exposes AssetPack id, range boundaries, cell count, proof root, source manifest root, access policy id/hash, ledger anchor state, and the pull-request delivery target after settlement. @@ -697,6 +731,33 @@ Acceptance criteria: - GitHub-only VCS readiness is honest; broader provider work remains outside V28 MVP QA unless required by the active surface. - realistic testnet BTD-AssetPack minting uses synthetic measurement receipts, Terminal journal rows, ledger anchors or ledger-observed placeholders, and database projections that can be diffed and repaired. - Bitcoin Taproot/PSBT posture is first-class; BSC/opBNB/Binance Web3 Wallet pilots remain future bridge/distribution work unless they are represented as disabled readiness or documentation notes. +- the next-gate Reading test contract is green and treated as acceptance + evidence: package Read-Need tests, depository-search tests, embedding/vector + search tests, Vercel Sandbox harness plan/host tests, pipeline-harness + route/preflight tests, Terminal stream adapter tests, and demonstration + local-fit tests all pass and remain mapped to this SPEC. +- bounded-real-inference readiness is tested separately from deterministic + mock posture. Mocked phase/agent/tool/ThricifiedGeneration outputs are admitted only when + they satisfy the same typed envelope, telemetry, and fail-closed contracts as + the live Reading pipelines. + +Implementation evidence: + +- `packages/btd/src/terminal-operational-health.ts` is the canonical package + adapter for Terminal operational health. It builds the displayed lane, + telemetry, upgrade, provider, settlement-network, synthetic testnet minting, + Terminal journal, ledger anchor, and ledger/database reconciliation read + from BTD primitives rather than a UI-only fixture. +- `uapi/app/terminal/TerminalOperationalHealthPanel.tsx` renders that package + read in Terminal. It keeps all six lanes visible, blocks value-bearing + mainnet without an operational approval root, exposes broadcaster/observer + severity, displays rollback/migration/generated-type refresh posture, shows + GitHub as the active VCS path, and marks BSC/opBNB/Binance Web3 Wallet pilots + disabled. +- Gate 7 implementation tests are + `packages/btd/__tests__/terminal-operational-health.test.ts` and + `uapi/tests/terminalOperationalHealthPanel.test.tsx`; package-level BTD tests + are runnable through `pnpm -C packages/btd test`. ### Gate 8: V28 Promotion Proof @@ -817,29 +878,39 @@ V28 separates Reading into distinct reviewable stages so readers can decide whether Bitcode understood the need before Bitcode exposes or settles source-bearing AssetPack material. -The staged Reading path is: - -1. **Need synthesis.** The user Read request is input to a Need pipeline. The - pipeline synthesizes a measured Need object with requirements, closure - criteria, failure modes, target artifact kinds, source constraints, - proof/settlement expectations, and pricing measurement inputs. Terminal - shows this Need in full detail. The user can accept it or request another - synthesis with feedback. No depository Fit search, BTC fee, BTD mint, or +The Terminal enterprise Reading path is exactly five user steps: + +1. **Request Read.** The enterprise reader supplies the repository/source + scope and exact request. Bitcode records the source revision, target + artifact kinds, closure criteria, and failure modes as + `ReadNeedComprehensionSynthesis.request` input. +2. **Review synthesized Need.** `ReadNeedComprehensionSynthesis` synthesizes a + measured `bitcode.read.need` with requirements, closure criteria, failure + modes, target artifact kinds, source constraints, proof/settlement + expectations, pricing measurement inputs, prompt telemetry, and feedback + history. Terminal shows the full Need; the user can accept it or request + resynthesis with feedback. No depository search, BTC fee, BTD mint, or AssetPack source disclosure is allowed before Need acceptance. -2. **Need-Fit synthesis.** An accepted Read-Need is input to the AssetPack - synthesis pipeline. This stage searches the deposited asset-space, ranks - candidates, measures candidate fit against the Need measurement, synthesizes - the candidate AssetPack, and returns `worthy_fit`, `no_worthy_fit`, or - `blocked_readiness` with proof-bearing search/fit telemetry. -3. **Source-safe preview.** A worthy synthesized AssetPack is previewed without - leaking the full source. Preview may show Need measurement, Fit measurement, - selected candidate ids, roots, score bands, proof posture, ownership - boundary, settlement boundary, and price; it must not show protected source - material before payment/right settlement. -4. **Settle and unlock.** Settlement records the reader BTC fee, mints or - assigns the BTD range according to the standing supply law, writes ownership - and read-license rows, anchors/journals the event, and only then unlocks the - full AssetPack source/right surface to the buyer/reader. +3. **Request Fit.** `ReadFindingFitsSynthesis` accepts only the latest accepted + Need. The pipeline verifies Need schema/review/measurement roots, prepares + setup and read-comprehension context, then the discovery phase searches the + Depository through lexical and vector channels, admits every deposit whose + source-bound score, confidence, proof, measurement, and readback posture are + above the configured thresholds, ranks those fits, and returns + `worthy_fit`, `no_worthy_fit`, or `blocked_readiness`. +4. **Review synthesized AssetPack.** When a worthy fit exists, + `ReadFindingFitsSynthesis` implementation uses the discovered fit deposits + as contextual knowledge to synthesize a source-safe AssetPack preview for + the accepted Need. Preview may show Need measurement, Fit measurement, fit + deposit ids, roots, score bands, proof posture, ownership boundary, + settlement boundary, and price; it must not show protected source material + or source-bearing patch contents before payment/right settlement. +5. **Buy AssetPack, settle.** Settlement records the reader BTC fee, mints or + assigns the BTD range according to the standing supply law, transfers or + licenses the BTD rights to the reader who paid for the AssetPack, writes + ownership and read-license rows, anchors/journals the event, verifies + database/ledger readback, and only then unlocks and delivers the full + source-bearing AssetPack as a pull request to the reading repository. The reader question therefore splits into two explicit operator questions: @@ -847,23 +918,23 @@ The reader question therefore splits into two explicit operator questions: - Did Bitcode find and synthesize a match good enough to preview confidently and pay for in BTC without source-IP leakage? -These stages are separately addressable in Terminal and separately journaled in +These five steps are separately addressable in Terminal and separately journaled in the database/ledger readback. A Need stage run cannot be reused as a Fit result unless the accepted Need id, Need measurement root, user review state, feedback -history, and synthesis telemetry are present. A Need-Fit run cannot be promoted -unless the input Need id and accepted Need measurement root match the latest -accepted Need revision. Preview cannot unlock source-bearing payloads. +history, and synthesis telemetry are present. A Finding Fits run cannot be +promoted unless the input Need id and accepted Need measurement root match the +latest accepted Need revision. Preview cannot unlock source-bearing payloads. Settlement cannot proceed unless the previewed AssetPack id, fee quote, BTD range projection, wallet authorization, BTC fee transaction, ownership boundary, journal entry, and ledger/database readback all agree. The product pipeline now carries a typed `bitcode.read.need` object before -Need-Fit search. The Need object contains `needId`, `measurementRoot`, +Finding Fits search. The Need object contains `needId`, `measurementRoot`, requirements, closure criteria, failure modes, target artifact kinds, source constraints, proof expectations, pricing measurement inputs, review state, and -feedback history. Strict Need-Fit execution is admitted only when +feedback history. Strict Finding Fits run is admitted only when `acceptedReadNeed` is present and has `reviewState='accepted'`; otherwise the -pipeline returns `blocked_readiness` before depository candidate recall. The +pipeline returns `blocked_readiness` before depository discovery. The Vercel Sandbox harness synthesizes and accepts a Need before invoking the bounded source-bound AssetPack pipeline so the closed staging-testnet evidence path remains runnable while Terminal moves to the separate user review step. @@ -872,13 +943,136 @@ path remains runnable while Terminal moves to the separate user review step. callers. The protocol demonstration carries only the minimal deterministic witness of -this path: local Need synthesis, explicit Need acceptance, local Need-Fit +this path: local Need synthesis, explicit Need acceptance, local Finding Fits ranking over fixture deposits, source-safe preview, and deterministic fee-quote shape. It must remain self-contained and must not import the product pipeline, registry, prompt, agent, Vercel, Supabase, UAPI, or package implementations. Product code may compare against the witness, but it must own its own runtime implementation. +### Reading pipeline contract inventory + +`packages/pipelines/asset-pack/src/reading-pipeline-contract.ts` is the +source-owned inventory for V28 Reading pipeline names, prompt/agent/tool +contracts, PTRR step ids, ThricifiedGeneration ids, return types, telemetry +fields, and proof counts. +Specification, Terminal UX, API route telemetry, and tests must stay aligned to +that inventory. + +`ReadNeedComprehensionSynthesis` has four phases, four PTRR agents, sixteen +declared PTRR steps, forty-eight ThricifiedGeneration units, four +model-structured PTRR steps, and no tools: + +| Phase | PTRR agent | Agent objective | Return type | +| --- | --- | --- | --- | +| `ReadNeedComprehensionSynthesis.request` | `ReadNeedComprehensionSynthesis.request.normalize` | normalize source context: extract read prompt, bind source revision, normalize target artifact kinds | `ReadNeedSourceInput` | +| `ReadNeedComprehensionSynthesis.comprehend` | `ReadNeedComprehensionSynthesis.comprehend.need-synthesizer` | synthesize Need: classify intent, bound requirements, bound non-goals, derive closure | `ReadNeed` | +| `ReadNeedComprehensionSynthesis.measure` | `ReadNeedComprehensionSynthesis.measure.need-measurement` | weighted requested volume: semantic relevance, source binding, artifact fit, closure specificity | `ReadNeedPricingMeasurementInputs` | +| `ReadNeedComprehensionSynthesis.review` | `ReadNeedComprehensionSynthesis.review.operator-review` | accept or resynthesize: render full Need, record feedback, accept Need, compute acceptance root | `AcceptedReadNeed` or `ResynthesisRequestedReadNeed` | + +Every PTRR agent above owns exactly four PTRR steps: `plan`, `try`, `refine`, +and `retry`. Every PTRR step owns three ThricifiedGeneration units for +`prepare-concise-context`, `chunk-then-sum`, and `stitch-until-complete`; each +ThricifiedGeneration is the strict reason, judge, structured-output sequence. + +`ReadFindingFitsSynthesis` has seven phases, eight PTRR agents, thirty-two +declared PTRR steps, ninety-six ThricifiedGeneration units, sixteen +model-structured PTRR steps, and four tool contracts: + +| Phase | PTRR agent | Agent objective | Return type | +| --- | --- | --- | --- | +| `ReadFindingFitsSynthesis.admit` | `ReadFindingFitsSynthesis.admit.accepted-need-gate` | verify accepted Need: schema, review state, measurement root | `ReadFindingFitsAdmission` | +| `ReadFindingFitsSynthesis.prepare` | `ReadFindingFitsSynthesis.prepare.setup-plan` | setup plan: summarize Read, source revision, fit boundaries | `PlanSchema` | +| `ReadFindingFitsSynthesis.prepare` | `ReadFindingFitsSynthesis.prepare.read-comprehension` | comprehend Read: primary intent, satisfaction criteria, entities, risk admission | `BoundedReadComprehensionSchema` | +| `ReadFindingFitsSynthesis.discovery` | `ReadFindingFitsSynthesis.discovery.finding-fits` | discover fit deposits: build query, lexical search, vector search, admit qualifying fit deposits, rank fit deposits | `DepositoryFitsResult` | +| `ReadFindingFitsSynthesis.implementation` | `ReadFindingFitsSynthesis.implementation.asset-pack` | structured AssetPack synthesis from fit deposits: plan pack, write evidence, bind proof, prepare preview | `AssetPackSynthesisOutput` | +| `ReadFindingFitsSynthesis.validate` | `ReadFindingFitsSynthesis.validate.fit-quality` | source/proof readiness: validate search, synthesis, disclosure, finish readiness | `ReadyToFinishOutput` | +| `ReadFindingFitsSynthesis.preview` | `ReadFindingFitsSynthesis.preview.source-safe-preview` | measure fee and lock source: Share-to-Fee, range projection, access policy, source lock | `AssetPackSourceSafePreview` | +| `ReadFindingFitsSynthesis.settle` | `ReadFindingFitsSynthesis.settle.buy-deliver` | buy and deliver: reader BTC fee, range/license/journal readback, pull request, completion | `AssetPackCompletionOutput` | + +The required tool contracts are +`ReadFindingFitsSynthesis.tool.lexical-depository-search`, +`ReadFindingFitsSynthesis.tool.vector-depository-search`, +`ReadFindingFitsSynthesis.tool.verification-evidence`, and +`ReadFindingFitsSynthesis.tool.vcs-create-pull-request`. Every inference telemetry +line must expose prompt template, interpolated prompt/messages, raw response, +parsed typed output, schema/return type, usage/timing, execution state, and +fail-closed status. Every tool telemetry line must expose tool id, input +posture, output posture, error posture, result state, and execution state. +The contract encodes `ptrrStepId` as `...`, +`thricifiedGenerationIds` as +`.thricified-generation....`, +reason/judge/structured-output prompt ids under +`.prompt.....*`, +and telemetry ids as `.telemetry.`, so tests can reject +generic or ambiguous phase evidence before promotion. + +Pipeline run diagram: + +```text +Terminal step 1: request Read + -> ReadNeedComprehensionSynthesis.request + -> ReadNeedComprehensionSynthesis.comprehend + -> ReadNeedComprehensionSynthesis.measure +Terminal step 2: review synthesized Need + -> ReadNeedComprehensionSynthesis.review + -> accepted bitcode.read.need +Terminal step 3: request Fit + -> ReadFindingFitsSynthesis.admit + -> ReadFindingFitsSynthesis.prepare + -> ReadFindingFitsSynthesis.discovery +Terminal step 4: review synthesized AssetPack + -> ReadFindingFitsSynthesis.implementation + -> ReadFindingFitsSynthesis.validate + -> ReadFindingFitsSynthesis.preview +Terminal step 5: buy AssetPack, settle + -> ReadFindingFitsSynthesis.settle + -> BTC fee readback + BTD range/license/journal readback + pull request +``` + +Terminal UX diagram: + +```text +[1 Request Read] + low-detail form: repository, branch/commit, Read Request, target artifacts + expandable detail: source revision, parser, closure criteria, failure modes +[2 Review Need] + low-detail review: synthesized Need summary, requirements, non-goals + expandable detail: prompt template, interpolated context, parsed Need, + measurement vector, feedback history, resynthesis attempts, acceptance root +[3 Request Fit] + low-detail action: run Fit against accepted Need + expandable detail: accepted Need id/root, depository search query, blockers +[4 Review AssetPack] + low-detail preview: fit score band, rationale, fee quote, source-safe roots + expandable detail: fit deposit ranking, proof posture, range projection, + disclosure policy, source lock, no-fit/blocked reasons +[5 Buy AssetPack, Settle] + low-detail action: authorize BTC fee and unlock delivery after readback + expandable detail: PSBT/finality state, ownership/license rows, journal, + anchor/reconciliation posture, pull-request target +``` + +Prompt and return-type audit: + +| Pipeline | Model-structured PTRR agents | Model-structured PTRR steps | Model prompt templates | Parsed return types | +| --- | ---: | ---: | --- | --- | +| `ReadNeedComprehensionSynthesis` | 1 | 4 | `ReadNeedComprehensionSynthesis.prompt.need-synthesis` | `ReadNeed` | +| `ReadFindingFitsSynthesis` | 4 | 16 | `ReadFindingFitsSynthesis.prompt.setup-plan`; `ReadFindingFitsSynthesis.prompt.read-comprehension`; `ReadFindingFitsSynthesis.prompt.asset-pack-synthesis`; `ReadFindingFitsSynthesis.prompt.fit-quality-validation` | `PlanSchema`; `BoundedReadComprehensionSchema`; `AssetPackSynthesisOutput`; `ReadyToFinishOutput` | + +Every model-structured PTRR step records the common prompt registry posture: +`factoryAgentWithPTRR` with the `prompt` + `stepPrompts` carrier, agent +prompt id, PTRR step prompt id, prompt template id, interpolated context, raw +response, parsed typed output, and the ThricifiedGeneration reason, judge, and +structured-output prompt ids for all three failsafe contexts. + +Tool audit: + +| Pipeline | Tool count | Tool ids | Return types | +| --- | ---: | --- | --- | +| `ReadNeedComprehensionSynthesis` | 0 | none | none | +| `ReadFindingFitsSynthesis` | 4 | `ReadFindingFitsSynthesis.tool.lexical-depository-search`; `ReadFindingFitsSynthesis.tool.vector-depository-search`; `ReadFindingFitsSynthesis.tool.verification-evidence`; `ReadFindingFitsSynthesis.tool.vcs-create-pull-request` | `DepositorySearchResult`; `EmbeddingSearchResult`; `AssetPackVerificationEvidenceResult`; `PullRequestDeliveryResult` | + ### Share-to-Fee measurement clarity V28 keeps the standing supply law intact while sharpening the initial @@ -886,7 +1080,7 @@ deterministic pricing/readiness interface. The initial Share-to-Fee calculation must be deterministic from recorded measurements: - each Need measurement vector dimension has a volume and a measurement weight; -- each candidate Fit measurement vector is evaluated against the accepted Need +- each fit deposit measurement vector is evaluated against the accepted Need measurement vector; - the priced measurement volume is the weighted admitted volume after fit, quality, proof, and settlement eligibility gates; @@ -909,20 +1103,60 @@ Gate 4 implementation state: acceptance actions while preserving the prior Read review boundary. The response stores prompt input, interpolated source context, parsed Need, measurement root, review state, feedback, and synthesis telemetry. -- Terminal live Need-Fit execution requires an accepted `bitcode.read.need` +- Read-Need test coverage must stay implementation-guiding rather than + snapshot-only: it must assert typed Need fields, measurement roots, + source-disclosure constraints, pricing measurement vectors, acceptance + roots, feedback/resynthesis handling, route telemetry shape, and the rule + that raw Read requests cannot directly enter strict Finding Fits run. +- Terminal live Finding Fits run requires an accepted `bitcode.read.need` object in addition to Deposit and admitted-Read activity ids. The live harness request carries `acceptedReadNeed` and - `requireAcceptedReadNeed=true`, so depository candidate recall cannot run + `requireAcceptedReadNeed=true`, so depository discovery cannot run from a raw Read request. - The AssetPack pipeline package owns the source-safe preview contract: - preview id, AssetPack id, Need root, Fit roots, selected candidate ids, score + preview id, AssetPack id, Need root, Fit roots, fit deposit ids, score band, disclosure policy, access policy id/hash, deterministic Share-to-Fee quote root, BTD range projection, settlement boundary, and locked/unlocked read-right state. +- Finding Fits test coverage must assert depository search semantics before + synthesis: embedding policy, vector dimensions, search RPC, source-bound + fit deposit normalization, proof/measurement/readback blockers, mock/frontier + leakage rejection, worthy/no-worthy/blocked result states, selection trace, + and Execution-store persistence of search and fit evidence. - The Vercel Sandbox harness writes that source-safe preview into the exported evidence artifact before ledger settlement readback. Terminal can stream the preview, fee quote, range projection, access state, ledger state, and pull-request target without exposing protected source before settlement. +- Harness and Terminal tests must assert the implementation envelope for the + live path: sandbox plan generation, detached sandbox command polling, artifact + readback, real-inference preflight, Supabase REST/DB lane matching, secret + redaction, accepted-Need forwarding, rich stream summarization, generation + and tool telemetry, and canonical stream-panel adaptation. +- The protocol-demonstration local loop remains the minimal independent + witness. Its tests must cover local Need synthesis, explicit Need acceptance, + accepted Finding Fits ranking, source-safe preview, deterministic fee quote, + no-worthy-fit, and blocked-readiness proof failures without importing product + packages or service adapters. + +Gate 7 Reading pipeline coverage contract: + +- The next gate cannot close unless the focused Reading suites pass together: + `packages/pipelines/asset-pack` Read-Need and depository-search tests, + `packages/pipeline-hosts` manifest/harness/host tests, UAPI read-review, + pipeline-harness route/preflight, Terminal harness stream, vector search + tests, and `protocol-demonstration` local fit-finding tests. +- The coverage set must be updated whenever a Reading phase, agent, tool, + PTRR step, ThricifiedGeneration id, telemetry field, algorithmic scoring rule, fee formula, + settlement boundary, or source-disclosure rule changes. Specification text, + tests, and core algorithmic design must move together; deficiencies found by + tests require either implementation repair or explicit SPEC correction before + gate acceptance. +- A mocked pipeline phase is acceptable only as a typed contract witness. It + must emit the same phase id, agent id, PTRR step id, ThricifiedGeneration ids, tool name, prompt + input, interpolated context, model output posture, parsed output type, + timing/usage shape, result state, and fail-closed errors expected from the + real phase. Tests must reject untyped placeholders that could hide live + pipeline drift. ### Long-running full-profile pipeline gate @@ -933,7 +1167,7 @@ window. `BITCODE_ASSET_PACK_REAL_INFERENCE_PROFILE=full` is scoped to a subsequent V28 gate. That later gate may run for dozens of minutes in Vercel Sandbox. The sandbox -execution itself must push finished result state, artifacts, and readback +pipeline command must push finished result state, artifacts, and readback signals to a server-side stream/socket handler or durable queue; the route that starts the run must not be responsible for waiting synchronously for the final result. The push must be correlated by the pipeline run id, authenticated @@ -944,7 +1178,7 @@ artifact/readback state after the starter HTTP request has ended. Full-profile acceptance requires the same evidence as bounded profile plus durable async completion delivery, resumable status readback, explicit timeout -and retry accounting, and Terminal reattachment to the live execution stream. +and retry accounting, and Terminal reattachment to the live pipeline stream. Until that gate exists, `full` is a preflight blocker on the deployed route and `bounded` remains the current staging-testnet profile for closing the source-bound Read/Fit -> AssetPack -> Finish -> ledger readback gate. @@ -973,7 +1207,7 @@ Current accepted boundaries: V28 productizes Terminal reads without redefining i ### Fit, recall, ranking, and verification -Current canonical objects and emitted artifacts: Fit review rows, depository search results, recall candidates, verification decisions, selected candidate ids, query roots, ranking roots, embedding policy. +Current canonical objects and emitted artifacts: Fit review rows, depository search results, recall candidates, verification decisions, fit deposit ids, query roots, ranking roots, embedding policy. Current algorithms and derivation rules: deposit-to-read fit, recall, vector/lexical source-bound ranking, proof/measurement gating, and verification decisions are explicit before settlement. Current invariants and fail-closed conditions: no-survivor asset pack blocks range commitment; broad Reads block readiness; unrelated Reads return no-worthy-fit; mock/frontier repository leakage blocks readiness; embedding model/dimension drift blocks vector recall promotion. Current proof obligations: prove candidate qualities, rejection reasons, source revision binding, proof posture, measurement posture, embedding model/dimensions/vector store, and ranking determinism. diff --git a/BITCODE_SPEC_V28_NOTES.md b/BITCODE_SPEC_V28_NOTES.md index 42d540105..9d25663f2 100644 --- a/BITCODE_SPEC_V28_NOTES.md +++ b/BITCODE_SPEC_V28_NOTES.md @@ -99,7 +99,7 @@ The active QA scenario is therefore single-deposit: - data-space: the latest deposited repository revision row, currently exercised with the `engineeredsoftware/ENGI` fixture, with repository, branch, commit, - signer, wallet authorization, and asset candidate evidence; + signer, wallet authorization, and asset fit deposit evidence; - Read: determine whether the deposited repository contains a complete Terminal-backed path from wallet/GitHub readiness through Deposit, Read/Fit, AssetPack evidence, proof/finality readback, and Supabase/ledger @@ -148,7 +148,7 @@ The runner must also terminate itself before caller timeout by emitting a `PipelineHarnessTimeoutError` blocked-readiness artifact with the last execution and stream events. -The minimum commercial telemetry record for a Read/Fit pipeline execution is: +The minimum commercial telemetry record for a Read/Fit pipeline run is: - one run row in `pipeline_runs` or `deliverable_pipeline_runs`; - event rows in `execution_events`, `stream_logs`, or @@ -161,7 +161,7 @@ The minimum commercial telemetry record for a Read/Fit pipeline execution is: generation context; - tool rows in `deliverable_pipeline_tool_executions`, including input, output, error, timing, phase, and agent context; -- fit/depository output carrying `depositorySearch`, candidate ranking, +- fit/depository output carrying `depositorySearch`, fit deposit ranking, `fitResult`, `queryRoot`, `rankingRoot`, selected candidate asset ids, and the embedding policy. @@ -512,6 +512,18 @@ The minimum useful V28 gate plan is Protocol/Terminal-MVP-first: 7. **Gate 7: Terminal Operations And Testnet Readiness** - Regtest/signet harness, telemetry sinks, alert panels, upgrade readiness, and rollback posture become Terminal-operated. - Taproot/PSBT/BTC remains the V28 crypto lane; BSC/opBNB/Binance/BitVM bridge work is future bridge/distribution readiness unless proof-bound artifacts exist. + - Terminal operational health now reads from package BTD primitives through + `terminal-operational-health.ts`, then renders lane approval, telemetry, + upgrade, generated type refresh, VCS, settlement-network, and synthetic + testnet mint/reconciliation state in Terminal. + - Reading pipeline test coverage becomes a gate contract: Read-Need, + Finding Fits, depository search, embedding/vector search, sandbox harness, + route preflight, Terminal stream adaptation, and demonstration local + fit-finding suites must pass together. + - Valid mocks must carry the same typed phase/agent/tool/PTRR/ThricifiedGeneration, + prompt/context, parsed-output, usage/timing, and fail-closed envelopes as + live Reading pipeline telemetry. Placeholder mocks are not acceptance + evidence. 8. **Gate 8: V28 Promotion Proof** - SPEC, DELTA, NOTES, PARITY, and PROVEN exist. diff --git a/BITCODE_SPEC_V28_PARITY_MATRIX.md b/BITCODE_SPEC_V28_PARITY_MATRIX.md index bbc03166e..73409ae32 100644 --- a/BITCODE_SPEC_V28_PARITY_MATRIX.md +++ b/BITCODE_SPEC_V28_PARITY_MATRIX.md @@ -114,17 +114,41 @@ It links manual screenshots, console logs, server logs, Supabase SQL checks, and | BTC/D identity and wallet-backed session | `uapi/app/api/wallet/_shared.ts`, `uapi/app/api/wallet/authenticate/route.ts`, `uapi/app/api/wallet/oauth/authorization-code/route.ts`, `uapi/app/api/wallet/oauth/token/route.ts`, `uapi/app/api/wallet/oauth/userinfo/route.ts`, `uapi/lib/bitcoin-wallet-client.ts`, `uapi/lib/bitcode-wallet-local.ts`, `packages/btd/src/wallet.ts`, `packages/btd/src/bitcoin-provider.ts`, `supabase/migrations/003_user_connections_provider_scope.sql`, `uapi/tests/bitcoinWalletClient.test.ts` | staging prerequisite pass for Leather; BTC fee signing still pending | May 14 live staging SQL confirmed `auth.users`, `auth.identities`, `user_profiles.settings.bitcodeProfile.walletBinding`, and `user_connections.provider='leather'` for the same wallet-backed user. Xverse/manual-provider parity and Terminal BTC fee signing remain closure evidence before BTC fee usage depends on them. | | GitHub App and repository source scope | `uapi/app/api/vcs/_shared.ts`, `uapi/app/api/vcs/[provider]/callback/route.ts`, `uapi/app/api/vcs/[provider]/setup/route.ts`, `uapi/app/api/vcs/[provider]/repositories/route.ts`, `packages/api/src/vcs/github-service.ts`, Auxillaries Externals pane, `supabase/migrations/001_v26_production.sql` `user_connections` and `vcs_repositories` | staging prerequisite pass | May 14 live staging SQL confirmed GitHub installation `132358627`, account `engineeredsoftware`, provider-scoped connection data, and `public.vcs_repositories` rows including `engineeredsoftware/ENGI`. Terminal Deposit/Read must now prove that repository inventory is used as live source scope. | | Terminal terminology parity | `BITCODE_SPEC_V28.md` terminology contract, `uapi/app/terminal/terminal-workspace-copy.ts`, `uapi/app/terminal/TerminalDepositComposer.tsx`, `uapi/app/terminal/TerminalReadScenarioPanel.tsx`, `uapi/components/base/bitcode/layout/bitcode-public-copy.ts`, Terminal/public-shell/docs tests, `supabase/migrations/20260515143000_v28_deposit_read_data_contract.sql` | closed for UI, docs, tests, API, ORM, demonstration, and data-contract carriers | Active V28 UI, docs, QA prose, OpenAPI, test-visible assertions, BTD journal kinds, ORM types, Supabase queries, and live schema use Deposit/Depositing and Read/Reading. The migration applied to staging renames range linkage to `read_id` and journal writes to `read_submission`; source audit has no exact retired Terminal-domain vocabulary outside excluded legacy/build artifacts. | -| PSQL/Supabase data reality | `supabase/migrations/001_v26_production.sql`, `002_v27_btd_crypto_registry.sql`, `003_user_connections_provider_scope.sql`, `20260514173000_enable_pipeline_runs_rls.sql`, `20260514175000_enable_pipeline_runtime_rls.sql`, `20260515143000_v28_deposit_read_data_contract.sql`, `packages/orm/src/data-health/*`, `packages/orm/scripts/run-data-health.ts`, `packages/orm/scripts/check-schema-types.ts`, `supabase/DATA_HEALTH.md`, `supabase/queries/*` | data-contract naming closed; Terminal Deposit/Read write evidence pending | Staging database has all required auth/profile/connection/repository/BTD registry/journal/telemetry tables, RLS posture does not hide required user-scoped reads, generated schema types cover canonical public tables, automated data-health checks pass, and QA SQL queries match Terminal/Wallet/Externals UI state. Current baseline: migration `20260515143000` is applied locally and remotely, `btd_asset_pack_ranges` exposes `read_id`, journal kind constraints accept `read_submission`, `pnpm db:data-health:daily` passes against staging, wallet/GitHub SQL evidence is green, and the Terminal-specific saved queries gate Deposit/Read/Fit write-read parity. | -| Fit-finding | `packages/pipelines/asset-pack/src/depository-search.ts`, `packages/pipeline-hosts`, `uapi/app/terminal/TerminalDepositReadWorkbench.tsx`, `TerminalReadScenarioPanel.tsx`, `terminal-deposit-read-workbench.ts`, `terminal-read-scenarios.ts`, `protocol-demonstration/src/local-fit-finding.js` reference witness | partial, V28 critical path | Terminal simplest Read produces or reads Fit candidates, review decision, qualities, rejection/block reasons, source roots, proof/dedupe roots, query/ranking roots, embedding policy, and visible readiness state without relying on demonstration runtime imports. Commercial closure requires the Vercel Sandbox harness to run the real AssetPack pipeline and persist phase/agent/generation/tool evidence. | -| Single-deposit commercial Read/Fit QA | `BITCODE_SPEC_V28.md` single-deposit commercial Reading QA, `BITCODE_V28_QA.md` Pass 2B, `supabase/queries/v28_qa_terminal_06_read_fit_quality_after_read.sql`, `uapi/tests/terminalActivityHistory.test.ts` | opened as the next critical V28 QA gate | Against the current single deposited repository revision, Terminal must prove Deposit before Read, Read before Fit, repository/branch/commit alignment, no `frontier/*` or mock leakage, worthy-fit AssetPack synthesis or explicit no-worthy-fit/blocker result, and honest settlement/finality readback. The current fixture is ENGI, but implementation must remain source-generic. | -| AssetPack synthesis and deterministic pipeline configuration | `packages/pipelines/asset-pack`, `packages/pipeline-hosts`, `internal-docs/ASSETPACK_EXECUTION.md`, `packages/protocol/src/canonical/run-artifacts.js`, `packages/btd/src/semantic-volume.ts`, `measuremint.ts`, `range.ts`, `receipts.ts`, `replay.ts`, `uapi/app/api/btd/mint-draft/route.ts`, Terminal AssetPack components | partial with blocker | Staging Read/Fit path creates or reads AssetPack evidence, semantic volume, measuremint/range or zero-cell receipt, and access-policy hash using protocol-specified model/pipeline configuration. Any broad user model-selection UI that can affect ledgerized synthesis must be removed or scoped to non-ledgerized conversation UX. V28 requires successful harness evidence before a synthesized AssetPack can be treated as commercially delivered. | -| Ledgerized journal and ledger anchors | `packages/btd/src/terminal-journal.ts`, `ledger-anchor.ts`, `reconciliation.ts`, `uapi/app/api/btd/terminal-journal/route.ts`, `asset-pack-ledger-anchor/route.ts`, `ledger-database-reconciliation/route.ts`, `supabase/migrations/002_v27_btd_crypto_registry.sql` | partial | Terminal shows journal rows for Read/Fit/AssetPack/BTC fee/anchor/reconciliation events, ledger anchor or blocked-readiness state is explicit, and SQL evidence in `btd_terminal_journal_entries`, `btd_asset_pack_ledger_anchors`, and reconciliation repair tables matches the UI. | -| BTC fee and testnet ledger path | `packages/btd/src/bitcoin-fees.ts`, `bitcoin-provider.ts`, `uapi/app/api/btd/btc-fee-transaction/route.ts`, wallet client code | partial | Testnet path prepares fee state, uses wallet-controlled signing/PSBT or named blocked-readiness receipt, records no server custody, and writes/readbacks `btc_fee_transactions` where a fee receipt is produced. | -| Pipeline runtime deployment reality | `packages/pipeline-hosts`, `uapi/app/api/pipeline-harness/asset-pack/route.ts`, `uapi/app/api/executions/*`, `uapi/app/api/external-realization/route.ts`, `uapi/app/api/make-bitcode-branch/route.ts`, `internal-docs/DEPLOYMENT.md`, `internal-docs/ASSETPACK_EXECUTION.md`, Terminal execution/readback components | partial, running harness under QA | Staging logs show the runtime lane selected, Vercel Sandbox execution creates/export artifacts, external realization or branch creation readiness is honest, source writes are blocked or performed by configured GitHub App authority, and Terminal state identifies runtime/deployment blockers instead of falling back to mock success. | -| Telemetry, logging, and Sentry/alerting | `uapi/lib/bitcode-qa-telemetry.ts`, `uapi/lib/bitcode-server-telemetry.ts`, `uapi/app/api/client-error/route.ts`, `packages/btd/src/telemetry.ts`, `packages/protocol/src/telemetry.js`, `btd_crypto_telemetry_events` migration | partial | Client/server verbose logs cover wallet, GitHub, Terminal Read/Deposit, Fit, AssetPack, ledger, and database sync; staging captures warnings/errors without leaking secrets; Sentry/alert sink readiness is documented or explicitly blocked for V35 deepening. | +| PSQL/Supabase data reality | `supabase/migrations/001_v26_production.sql`, `002_v27_btd_crypto_registry.sql`, `003_user_connections_provider_scope.sql`, `20260514173000_enable_pipeline_runs_rls.sql`, `20260514175000_enable_pipeline_runtime_rls.sql`, `20260515143000_v28_deposit_read_data_contract.sql`, `packages/orm/src/data-health/*`, `packages/orm/scripts/run-data-health.ts`, `packages/orm/scripts/check-schema-types.ts`, `supabase/DATA_HEALTH.md`, `supabase/queries/*`, `scripts/verify-v28-pipeline-readback.mjs` | closed for Gate 7 readback | Staging database has the required auth/profile/connection/repository/BTD registry/journal/telemetry tables for this gate. REST and DB readback against staging-testnet report populated pipeline, generation, tool, BTD range, BTC fee, journal, anchor, ownership, read-license, and crypto telemetry rows for the completed source-bound run. | +| Fit-finding | `packages/pipelines/asset-pack/src/depository-search.ts`, `packages/pipeline-hosts`, `uapi/app/terminal/TerminalDepositReadWorkbench.tsx`, `TerminalReadScenarioPanel.tsx`, `terminal-deposit-read-workbench.ts`, `terminal-read-scenarios.ts`, `protocol-demonstration/src/local-fit-finding.js` reference witness, V28 readback verifier | closed for current gate | Terminal simplest Read produces or reads Fit candidates, review decision, qualities, rejection/block reasons, source roots, proof/dedupe roots, query/ranking roots, embedding policy, and visible readiness state without relying on demonstration runtime imports. The Vercel Sandbox harness ran the real source-bound AssetPack pipeline and persisted phase, agent, generation, and tool evidence. | +| Single-deposit commercial Read/Fit QA | `BITCODE_SPEC_V28.md` single-deposit commercial Reading QA, `BITCODE_V28_QA.md` Pass 2B, `supabase/queries/v28_qa_terminal_06_read_fit_quality_after_read.sql`, `uapi/tests/terminalActivityHistory.test.ts`, V28 readback verifier | closed for current gate | Against the current single deposited repository revision, Gate 7 proof shows Deposit before Read, Read before Fit, repository/branch/commit alignment, no `frontier/*` or source-overlay leakage, worthy-fit AssetPack synthesis, PR delivery, and honest settlement/finality readback. The fixture is ENGI, but the implementation remains source-generic. | +| AssetPack synthesis and deterministic pipeline configuration | `packages/pipelines/asset-pack`, `packages/pipeline-hosts`, `internal-docs/ASSETPACK_EXECUTION.md`, `packages/protocol/src/canonical/run-artifacts.js`, `packages/btd/src/semantic-volume.ts`, `measuremint.ts`, `range.ts`, `receipts.ts`, `replay.ts`, `uapi/app/api/btd/mint-draft/route.ts`, Terminal AssetPack components | pipeline closed; broader model-selection cleanup remains | Staging Read/Fit path creates and reads AssetPack evidence, semantic volume posture, range receipt, access-policy hash, and model/tool telemetry using protocol-specified pipeline configuration. Broad user model-selection UI that can affect ledgerized synthesis must still be removed or scoped to non-ledgerized conversation UX before V28 promotion. | +| Ledgerized journal and ledger anchors | `packages/btd/src/terminal-journal.ts`, `ledger-anchor.ts`, `reconciliation.ts`, `uapi/app/api/btd/terminal-journal/route.ts`, `asset-pack-ledger-anchor/route.ts`, `ledger-database-reconciliation/route.ts`, `supabase/migrations/002_v27_btd_crypto_registry.sql`, V28 readback verifier | closed for current gate | Terminal shows journal rows for Read/Fit/AssetPack/BTC fee/anchor/reconciliation events, ledger anchor or blocked-readiness state is explicit, and SQL evidence in `btd_terminal_journal_entries`, `btd_asset_pack_ledger_anchors`, and reconciliation readback matches the UI. | +| BTC fee and testnet ledger path | `packages/btd/src/bitcoin-fees.ts`, `bitcoin-provider.ts`, `uapi/app/api/btd/btc-fee-transaction/route.ts`, wallet client code, V28 readback verifier | closed for current gate | Testnet path prepares fee state, records no server custody, and writes/readbacks `btc_fee_transactions`; the completed Gate 7 run recorded a prepared reader BTC fee row with depositor/reader boundary evidence. | +| Pipeline runtime deployment reality | `packages/pipeline-hosts`, `uapi/app/api/pipeline-harness/asset-pack/route.ts`, `uapi/app/api/executions/*`, `uapi/app/api/external-realization/route.ts`, `uapi/app/api/make-bitcode-branch/route.ts`, `internal-docs/DEPLOYMENT.md`, `internal-docs/ASSETPACK_EXECUTION.md`, Terminal execution/readback components | closed for bounded profile | Staging logs show the runtime lane selected, Vercel Sandbox execution creates and exports artifacts, branch creation/PR delivery uses configured GitHub authority, and Terminal state identifies runtime/deployment blockers instead of falling back to mock success. Full-profile async completion remains a later V28 gate. | +| Telemetry, logging, and Sentry/alerting | `uapi/lib/bitcode-qa-telemetry.ts`, `uapi/lib/bitcode-server-telemetry.ts`, `uapi/app/api/client-error/route.ts`, `packages/btd/src/telemetry.ts`, `packages/protocol/src/telemetry.js`, `btd_crypto_telemetry_events` migration, V28 readback verifier | closed for Gate 7 telemetry | Client/server verbose logs and persisted pipeline rows cover Terminal Read/Deposit, Finding Fits, AssetPack, ledger, and database sync for this gate; staging captures generation and tool telemetry without leaking secrets. Sentry/alert sink deepening remains a V35 documentation/telemetry concern. | | MCP API and ChatGPT App parity | `packages/mcp`, `packages/chatgptapp`, UAPI MCP/ChatGPT App entrypoints, docs routes | partial | MCP and ChatGPT App MVP read the same wallet, GitHub, access-policy, AssetPack, journal, and proof posture as Terminal and fail closed when prerequisites are absent. | | Legacy residue awareness and cleaning | source scans for retired workspace routes, `orbitals` carriers, broad model-selection UI, Exchange/website Conversations links, versioned route families | partial | Active V28 paths have no versioned route/source naming, no retired workspace route, no active old orbital shell, no Exchange/Conversations dependency for V28 closure, and no user-driven ledgerized model selection. Deferred compatibility carriers are named and scoped. | +## Enterprise Reading Pipeline Parity Matrix + +This gate folds any prior V28 Reading, Terminal stream, pipeline harness, and +source-safe AssetPack closure gap into one acceptance surface. The two +commercial pipeline names are canonical for this gate: +`ReadNeedComprehensionSynthesis` and `ReadFindingFitsSynthesis`. + +| Pipeline / UX surface | Source evidence | Current V28 judgment | Closure evidence required | +| --- | --- | --- | --- | +| Five-step Terminal Reading UX | `uapi/app/terminal/terminal-deposit-read-workbench.ts`, `uapi/app/terminal/TerminalDepositReadWorkbench.tsx`, `uapi/tests/terminalDepositReadWorkbench.test.ts`, local browser QA | closed for current gate | Terminal shows exactly: request Read, review synthesized Need, request Fit, review synthesized AssetPack, buy AssetPack/settle. Default state is low-detail and guided; all proof/telemetry/detail surfaces remain expandable. | +| `ReadNeedComprehensionSynthesis` contract inventory | `packages/pipelines/asset-pack/src/reading-pipeline-contract.ts`, `packages/pipelines/asset-pack/src/__tests__/reading-pipeline-contract.test.ts` | implemented in gate branch | Contract declares four phases, four PTRR agents, sixteen PTRR steps, forty-eight ThricifiedGeneration units, four model-structured PTRR steps, zero tools, all ids prefixed by `ReadNeedComprehensionSynthesis`, return types, prompt registry ids, prompt template, stores, and telemetry fields. | +| `ReadNeedComprehensionSynthesis` prompt/return audit | `reading-pipeline-contract.ts`, `bounded-structured-inference.ts`, `read-review/route.ts`, `readReviewRoute.test.ts` | implemented in gate branch | One model prompt template is declared and test-audited: `ReadNeedComprehensionSynthesis.prompt.need-synthesis` returning `ReadNeed` through four PTRR steps. Telemetry must carry prompt template, interpolated context, raw response posture, parsed typed output, schema, phase, agent, PTRR step, ThricifiedGeneration ids, and measurement/review roots. | +| `ReadNeedComprehensionSynthesis` route telemetry | `uapi/app/api/read-review/route.ts`, `uapi/tests/api/readReviewRoute.test.ts` | implemented in gate branch | `/api/read-review` synthesis responses include pipeline name, phase id, agent id, PTRR step id, ThricifiedGeneration ids, prompt template, prompt input, interpolated context, raw output posture, parsed typed output, measurement root, review state, and next action. Acceptance responses include acceptance root and `ReadFindingFitsSynthesis` as next pipeline. | +| `ReadNeedComprehensionSynthesis` algorithmic precision | `packages/pipelines/asset-pack/src/read-need.ts`, `packages/pipelines/asset-pack/src/__tests__/read-need.test.ts`, live local `/api/read-review` OpenAI validation | closed for current gate | Need construction remains exact to the Read Request: requirements, non-goals/failure modes, source constraints, target artifact kinds, proof expectations, pricing measurement vector, weighted requested volume, feedback history, resynthesis attempts, and acceptance root. Tests reject raw Read-to-Fit execution when strict Need acceptance is required, and live local OpenAI synthesis returns a measured Need through all three ThricifiedGeneration stages. | +| `ReadFindingFitsSynthesis` contract inventory | `packages/pipelines/asset-pack/src/reading-pipeline-contract.ts`, `packages/pipelines/asset-pack/src/__tests__/reading-pipeline-contract.test.ts` | implemented in gate branch | Contract declares seven phases, eight PTRR agents, thirty-two PTRR steps, ninety-six ThricifiedGeneration units, sixteen model-structured PTRR steps, four tools, all ids prefixed by `ReadFindingFitsSynthesis`, return types, prompt registry ids, prompt templates, stores, and telemetry fields. | +| `ReadFindingFitsSynthesis` prompt/return audit | `reading-pipeline-contract.ts`, setup/comprehension/synthesis/validation agents, `bounded-structured-inference.test.ts`, staging-testnet run `c38a98cf-403e-4fc7-9c9e-ba615d4af024` | closed for bounded profile | Four model prompt templates are declared and test-audited: setup plan, read comprehension, AssetPack synthesis, and fit-quality validation; each is exercised through Plan, Try, Refine, and Retry. Parsed return types are `PlanSchema`, `BoundedReadComprehensionSchema`, `AssetPackSynthesisOutput`, and `ReadyToFinishOutput`; staging-testnet readback proves model-generation rows, structured tool rows, and source-bound AssetPack completion for the bounded profile. | +| Prefix discipline below pipeline names | `reading-pipeline-contract.ts`, `reading-pipeline-contract.test.ts` | implemented in gate branch | Phase, agent, PTRR step, ThricifiedGeneration, tool, prompt, and telemetry identifiers are rejected unless nested under `ReadNeedComprehensionSynthesis` or `ReadFindingFitsSynthesis`; generic labels may remain only as UI summaries or typed payload keys, not as contract identifiers. | +| Depository Finding Fits discovery | `packages/pipelines/asset-pack/src/depository-search.ts`, `AssetPackLexicalDepositorySearchTool.ts`, `embedding-config.ts`, `depository-search*.test.ts`, `embedding-config.test.ts`, V28 readback verifier | closed for current gate | Accepted Need search covers lexical and vector contracts, source binding, proof/measurement/readback blockers, mock/frontier rejection, fit deposit ranking roots, query roots, worthy/no-worthy/blocked states, `fitDepositAssetIds`, `fitDeposits`, and compatibility selected-candidate aliases. Staging readback proves a worthy fit with query root, ranking root, selected candidate ids, and OpenAI `text-embedding-3-small` / 1536-dimensional policy. | +| AssetPack synthesis prompt telemetry | `bounded-structured-inference.ts`, setup/comprehension/synthesis agents, `packages/pipeline-hosts/src/asset-pack-harness.ts`, `uapi/app/terminal/terminal-pipeline-harness-client.ts`, V28 readback verifier | closed for bounded profile | Every bounded inference stores prompt template, interpolated prompt/messages, raw model response, parsed typed output, usage, provider/model, phase, agent, and step. Harness stream events preserve those fields as expandable Terminal metadata. Full PTRR profile remains behind the async completion gate until its push/readback transport is implemented. Implementation uses discovered fit deposits as contextual knowledge inputs for source-safe AssetPack synthesis. | +| Source-safe preview and Share-to-Fee | `packages/pipelines/asset-pack/src/read-need.ts`, `postprocess.ts`, `uapi/app/api/pipeline-harness/asset-pack/runner.ts`, `read-need.test.ts`, `pipelineHarnessRoute.test.ts`, V28 readback verifier | closed for current gate | Preview exposes Need/Fit measurements, admitted fit quality, fee quote/root, range projection, disclosure policy, access policy id/hash, settlement boundary, and locked source state without protected source leakage before settlement. | +| Buy AssetPack and settle | `uapi/app/api/pipeline-harness/asset-pack/runner.ts`, BTD fee/range/journal/reconciliation primitives, Terminal preview/readback UI, V28 readback verifier | closed for current gate | Reader BTC payment, depositor ownership boundary, read-license rows, range readback, journal entries, ledger anchor or blocked readiness, database projection, reconciliation, and PR delivery target agree before unlock. Source-bound staging-testnet readback proves BTD range, BTC fee, ownership/license, journal, anchor, crypto telemetry, and PR delivery rows for the latest completed run. | +| 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 implementation matrix | Area | Current source evidence | Judgment | Gate owner | @@ -154,16 +178,17 @@ It links manual screenshots, console logs, server logs, Supabase SQL checks, and | 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 | -| deployment lanes and telemetry surfaced in Terminal | `deployment-lanes.ts`, `telemetry.ts`, deployment-readiness route | implemented prerequisite | Gate 7 | -| migration/type refresh visible as readiness | V27 migrations and dashboard RLS migration are applied/aligned in staging Supabase; generated type refresh is deferred. | substantially advanced | Gate 7 | -| GitHub-only provider readiness disclosed | `internal-docs/INTEGRATIONS.md` shows GitHub implemented and broader providers incomplete | implemented prerequisite | Gate 7 | -| BTD-AssetPack testnet minting and ledgerized synthetic measurement | V27 package primitives exist for measuremint, range, receipts, ledger anchors, Terminal journal, and reconciliation. V28 must prove realistic testnet or testnet-readiness flow with synthetic measurement, BTD-AssetPack mint/read state, journal rows, ledger anchors or blocked ledger readiness, and projection/reconciliation readback. | substantially advanced | Gate 3 / Gate 5 / Gate 7 | -| Taproot/BNB/Binance research posture | current V28 sources center Bitcoin wallet/PSBT/Taproot-compatible providers; no BSC/opBNB/BEP-20/Binance Web3 Wallet BTD deployment artifact is bound. V28 documents Bitcoin/Taproot-first testnet posture and records BSC/opBNB/Binance/BitVM bridge pilots as future bridge/distribution work unless proof-bound artifacts are added. | accepted boundary | Gate 7 / V36+ | +| 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 | +| BTD-AssetPack testnet minting and ledgerized synthetic measurement | `terminal-operational-health.ts` builds a synthetic testnet AssetPack readback using measuremint, range allocation, mint receipt, Terminal journal rows, Bitcoin Taproot ledger anchor, observed/projected ledger facts, and reconciliation; `packages/btd/__tests__/terminal-operational-health.test.ts` proves diff/repair shape. | implemented | Gate 3 / Gate 5 / Gate 7 | +| Taproot/BNB/Binance research posture | Terminal operational health marks Bitcoin Taproot/PSBT ready and BSC, opBNB, and Binance Web3 Wallet disabled until a later Protocol gate admits proof-bound artifacts. | accepted boundary | Gate 7 / V36+ | ## V28 implementation checklist | Area | Required V28 result | Judgment | | --- | --- | --- | +| Terminal operations | lane approval posture, broadcaster/observer telemetry, upgrade/migration/generated-type refresh, GitHub-only VCS posture, Bitcoin Taproot/PSBT readiness, disabled Binance-family pilots, and synthetic testnet mint/read reconciliation are rendered from package primitives | implemented | | Draft family | SPEC, DELTA, NOTES, PARITY exist | closed | | Canon posture | V27 active / V28 draft in source posture carriers | closed | | Routes | unversioned UAPI route scan passes | closed | @@ -179,7 +204,6 @@ It links manual screenshots, console logs, server logs, Supabase SQL checks, and | Terminal Read/Fit | Read, Fit, semantic volume, measuremint, policy UX | pending | | Terminal range/read | AssetPack range and read-right detail | pending | | Terminal journal/reconcile | selected transaction Journal section separates ledger observations, database projections, canonical roots, repair receipts, and blocking drift reasons | substantially advanced | -| Terminal operations | telemetry, lanes, upgrade, migration readiness | pending | ## Later-Version Deferrals diff --git a/BITCODE_V28_QA.md b/BITCODE_V28_QA.md index bae49bac1..104787880 100644 --- a/BITCODE_V28_QA.md +++ b/BITCODE_V28_QA.md @@ -549,7 +549,7 @@ Automated verification after this implementation pass: - `pnpm -C uapi run test:e2e:commercial-mvp`: 50 passed after Conversations streaming, Conversations exit, and Terminal transaction-search stabilization. - `npm --prefix protocol-demonstration run test:integration`: 58 passed after standalone demonstration/package-boundary cleanup. - `npm --prefix protocol-demonstration run test:v27-crypto`: 9 passed after standalone demonstration/package-boundary cleanup. -- `npm --prefix protocol-demonstration run test:v28-mvp-qa`: 13 passed after adding the boundary-separation and local Need-Fit witness checks. +- `npm --prefix protocol-demonstration run test:v28-mvp-qa`: 13 passed after adding the boundary-separation and local Finding Fits witness checks. - `pnpm -C uapi exec jest --runInBand tests/demonstrationWitnessMount.test.tsx tests/demonstrationWitnessScopedStylesRoute.test.ts tests/terminalPreservedShellSurface.test.tsx tests/terminalShellBridge.test.tsx tests/marketingLandingPage.test.tsx tests/api/readReviewProtocolParity.test.ts tests/api/bitcodeAppContextOptions.test.ts tests/protocolCommercialBoundary.test.ts`: 18 passed after the formal protocol package split. - `node --test --test-force-exit protocol-demonstration/test/v28-boundary-separation.test.js`: 2 passed after the formal package split. - `pnpm -C packages/protocol test`: 2 passed after the formal package split and protocol runtime-source deployment fix. @@ -1024,9 +1024,9 @@ Manual steps: 5. Record the measured Read. Capture the `/api/executions/history` request and response; expected status is `201`, and the page must remain in the Read chain with a `measured` next-state message. -6. Admit the measured Read for fit search. Capture the `/api/executions/history` +6. Admit the measured Read for Finding Fits. Capture the `/api/executions/history` request and response; expected status is `201` with - `fitSearchAdmission.admitted=true`. + `findingFitsAdmission.admitted=true`. 7. Record Fit result posture. Capture the `/api/executions/history` request and response; expected status is `201`. 8. If a branch, AssetPack, BTC fee, ledger anchor, or settlement control becomes @@ -1093,7 +1093,7 @@ Blockers: reasons, or explicit blocker. - Negative controls produce a confident AssetPack instead of no-worthy-fit or clarification. -- Fit search proceeds before Read review/admission. +- Finding Fits proceeds before Read review/admission. - Pipeline output lacks `fit.resultState`, `fit.resultReasons`, query root, or ranking root after the AssetPack pipeline entrypoint reports completion. - Pipeline output uses an embedding model or dimensions that do not match the @@ -1325,7 +1325,7 @@ Pass criteria: - For the staging-testnet pass, generation rows must be non-zero because `BITCODE_ASSET_PACK_REAL_INFERENCE=1` is required. A completed run with zero generation rows is only deterministic bring-up evidence. -- Parsed/cast generation output is stored when a ThriceifiedGeneration parser +- Parsed/cast generation output is stored when a ThricifiedGeneration parser runs, so operators can compare raw model text with typed Fit/search evidence. - Query 03 still shows no AssetPack range, BTC fee, ledger anchor, settlement, or finality claim without matching projection rows. @@ -1357,8 +1357,8 @@ a synchronous route, raw-prompt Read, or source-leaking preview model. | Gate | Scope | Acceptance boundary | | --- | --- | --- | -| Need synthesis review | Split "What is the need?" from Fit search. The Read request enters a Need pipeline, producing requirements, closure criteria, failure modes, target artifact kinds, proof expectations, pricing measurement inputs, and a Need measurement root. | User can accept the Need or request resynthesis with feedback. No Fit search, source preview, BTC fee, or BTD settlement is allowed before Need acceptance. The accepted Need id, feedback history, telemetry, and measurement root become the only valid input to Fit search. | -| Need-Fit search and synthesis | Input is the accepted Read-Need, not raw prompt text. The AssetPack synthesis pipeline searches deposited supply, ranks candidates, measures Fit against the Need, and synthesizes the candidate AssetPack. | Result is `worthy_fit`, `no_worthy_fit`, or `blocked_readiness` with query root, ranking root, selected ids, proof/measurement posture, embedding policy, model/tool telemetry, and a source-safe candidate AssetPack id. | +| Need synthesis review | Split "What is the need?" from Finding Fits. The Read request enters a Need pipeline, producing requirements, closure criteria, failure modes, target artifact kinds, proof expectations, pricing measurement inputs, and a Need measurement root. | User can accept the Need or request resynthesis with feedback. No Finding Fits, source preview, BTC fee, or BTD settlement is allowed before Need acceptance. The accepted Need id, feedback history, telemetry, and measurement root become the only valid input to Finding Fits. | +| ReadFindingFitsSynthesis | Input is the accepted Read-Need, not raw prompt text. The AssetPack synthesis pipeline searches deposited supply, ranks candidates, measures Fit against the Need, and synthesizes the candidate AssetPack. | Result is `worthy_fit`, `no_worthy_fit`, or `blocked_readiness` with query root, ranking root, selected ids, proof/measurement posture, embedding policy, model/tool telemetry, and a source-safe candidate AssetPack id. | | Source-safe preview | Show enough proof to decide whether to pay without leaking source. | Preview may show Need/Fit measurements, score bands, roots, candidate ids, proof posture, ownership boundary, settlement boundary, and BTC quote. It must not expose protected AssetPack source before settlement. | | Settle and unlock | Deterministic Share-to-Fee and BTD settlement. | Price is derived from `sum(measurement_weight * measurement_volume * admitted_fit_quality)` and the staging fee schedule. Reader pays BTC fee, BTD range/ownership/license/journal/anchor rows are written and read back, then full AssetPack source/right surface is unlocked. | | Full-profile async pipeline | Run the full PTRR profile for long-running audits. | Vercel Sandbox execution may run for dozens of minutes and must push completion artifacts to a server-side stream/socket handler or durable queue; the push is run-id correlated, authenticated, idempotent, and durable before sandbox stop. Terminal can reattach and read final state without the starter route waiting. | @@ -1370,16 +1370,16 @@ a synchronous route, raw-prompt Read, or source-leaking preview model. failure modes, target artifact kinds, source constraints, proof expectations, pricing measurement inputs, review state, and feedback history. -- Strict Need-Fit search blocks before depository candidate recall unless an +- Strict Need-Finding Fits blocks before depository Finding Fits discovery unless an accepted Need is present. The blocked state is explicit `blocked_readiness` with `accepted_read_need_missing`. - The depository search path consumes accepted Need source constraints and - measurement roots as the Fit search input, rather than raw prompt text, when + measurement roots as the Finding Fits input, rather than raw prompt text, when `acceptedReadNeed` is supplied. - The Vercel Sandbox harness now lists Need stages in the manifest and synthesizes plus accepts a Need before invoking the existing source-bound AssetPack pipeline. This preserves the current proven staging-testnet path - while Terminal is split into user-visible Need review and Need-Fit execution + while Terminal is split into user-visible Need review and ReadFindingFitsSynthesis steps. - `buildShareToFeePreview` provides the initial source-safe quote shape from accepted Need measurement vector and admitted Fit quality: @@ -1388,7 +1388,7 @@ a synchronous route, raw-prompt Read, or source-leaking preview model. `resynthesize_read_need`, and `accept_read_need` actions. The synthesis response includes prompt input, interpolated source context, parsed Need, measurement root, review state, feedback, and telemetry; the acceptance - response returns the only admissible `acceptedReadNeed` for Need-Fit search. + response returns the only admissible `acceptedReadNeed` for Need-Finding Fits. - Terminal live harness requests now include `acceptedReadNeed` and `requireAcceptedReadNeed=true`. The live fit button remains blocked until a typed accepted Need is present, even when Deposit and admitted-Read activity @@ -1450,7 +1450,7 @@ Later staging-testnet evidence on 2026-05-17 after the no-overlay deployment: `engineeredsoftware/ENGI@main:ee1481634c985afbc349f8d8b837cd1c43a254ac` reached real model-backed setup execution. Umbrella pipeline run `44a05fd7-f337-42cf-ad3c-d4a607d54a2b` and deliverable pipeline run - `b1b04a2d-0376-4200-b08c-7936076f2566` failed before candidate recall or + `b1b04a2d-0376-4200-b08c-7936076f2566` failed before Finding Fits discovery or AssetPack synthesis. - Database readback showed real execution progress: 2 setup phase delegation rows, 16 agent-step rows, 72 model-generation rows, 1634 deliverable pipeline @@ -1521,7 +1521,7 @@ manifest-bound Deposit evidence root fixes: flags, with no manual `BITCODE_SANDBOX_DEPOSIT_*_ROOT` overrides. The harness materialized manifest-bound `proofRoot`, `measurementRoot`, and `reconciliationReadbackRoot` values before pipeline execution. -- Pipeline run `3b3339b4-5695-46fa-9ce4-2163c7eb1f11` reached candidate recall, +- Pipeline run `3b3339b4-5695-46fa-9ce4-2163c7eb1f11` reached Finding Fits discovery, model-backed setup, model-backed Read comprehension, model-backed synthesis, `pipeline-complete`, and `ledger-settlement-readback`. - The pipeline selected the proof-bearing candidate `manual-deposit-qa`, @@ -1743,6 +1743,47 @@ manifest-bound Deposit evidence root fixes: overlay evidence exists in the harness input, manifest, execution context, or `BITCODE_PIPELINE_SOURCE_OVERLAY_APPLIED=1`. +2026-05-19 local validation refresh for the same Gate 7 evidence: + +- Root `.env.local` remains the local staging-testnet source for local + application deployment and points REST and DB readback at project + `tkpyosihuouusyaxtbau`. The OpenAI generation lane is pinned to + `gpt-4.1-mini`, and the embedding lane is pinned to + `text-embedding-3-small` with 1536 dimensions. +- Direct OpenAI validation succeeded for both chat generation and embeddings. + The embedding response returned a 1536-dimensional vector for the configured + AssetPack depository policy. +- Live local `/api/read-review` `synthesize_read_need` completed through the + `ReadNeedComprehensionSynthesis` route using real OpenAI inference. The + response carried `thricified-generation` mode, `success` status, provider + `openai`, model `gpt-4.1-mini`, parsed reasoning, parsed judgment, parsed + structured output, a measured `ReadNeed`, and a `sha256:` measurement root. +- Local `/terminal` browser validation against `pnpm -C uapi dev:staging` + rendered all five Reading steps with no error overlay, console errors, or + page errors: `Request Read`, `Review synthesized Need`, `Request Fit`, + `Review synthesized AssetPack`, and `Buy AssetPack, settle`. The same page + rendered both pipeline names, + `ReadNeedComprehensionSynthesis` and `ReadFindingFitsSynthesis`. +- Real-model parsing now tolerates common non-contract surface variance without + weakening typed output: scalar list fields are normalized to arrays, and + nonnumeric PTRR confidence/quality labels fail closed to score `0` instead + of crashing the route. The typed `ReadNeed` and bounded structured inference + tests cover this behavior. +- `pnpm qa:v28:pipeline-readback -- --env-file .env.local --expected-host + tkpyosihuouusyaxtbau.supabase.co --lookback-hours 72` returned + `ready_for_v28_result_review` through REST readback: 11 pipeline rows, 8513 + deliverable events, 123 phase delegations, 418 agent steps, 122 generations, + 14 tool executions, and nonzero BTD range, BTC fee, journal, anchor, + ownership, read-license, and crypto telemetry rows. +- `pnpm qa:v28:pipeline-readback -- --env-file .env.local --expected-host + tkpyosihuouusyaxtbau.supabase.co --readback-source db --lookback-hours 72` + also returned `ready_for_v28_result_review`. The latest deliverable run is + `c38a98cf-403e-4fc7-9c9e-ba615d4af024`, status `completed`, with 695 + events, 13 phase delegations, 42 agent steps, 5 generations, and 4 tools. + The remaining warning is the accepted substitution for the absent generic + `phase_executions` table because the canonical deliverable phase hierarchy is + populated. + Pass 2C prompt-to-artifact closure audit: | Requirement | Current artifact/evidence | Gate state | @@ -1760,7 +1801,7 @@ Pass 2C prompt-to-artifact closure audit: | Clean deployed no-overlay run writes and rereads pipeline rows from the populated staging-testnet database. | Latest source-bound run `c38a98cf-403e-4fc7-9c9e-ba615d4af024` completed without source overlay and wrote/read back umbrella pipeline run `13bc9a38-0f94-446f-98d7-14474d13467a`, deliverable stream rows, phases, agent steps, generation rows, and structured tool rows. | closed for current gate | | Settlement writes and rereads BTD range, BTC fee, ownership/license, journal, anchor, and crypto telemetry rows. | AssetPack `asset-pack-c38a98cf-403e-4fc7-9c9e-ba615d4af024` was settled with BTD range `[5, 6)`, 546 sat reader BTC fee prepared without server custody, confirmed ledger anchor, depositor ownership event, reader license, crypto telemetry, and four Terminal journal rows. | closed for current gate | | Full-profile inference may run for dozens of minutes without route wait. | Scoped as a subsequent V28 gate requiring sandbox-pushed async completion to a server-side stream/socket handler or durable queue. The push must be run-id correlated, authenticated, idempotent, and durable before sandbox stop. | intentionally out of current gate | -| Demonstration remains a self-contained minimal Reading witness. | `protocol-demonstration/src/local-fit-finding.js` now models Need synthesis, Need acceptance, Need-Fit ranking, source-safe preview, and deterministic BTC fee quote without importing product pipeline code. `npm --prefix protocol-demonstration run test:v28-mvp-qa` passes 13 tests. | implemented and tested | +| Demonstration remains a self-contained minimal Reading witness. | `protocol-demonstration/src/local-fit-finding.js` now models Need synthesis, Need acceptance, Finding Fits ranking, source-safe preview, and deterministic BTC fee quote without importing product pipeline code. `npm --prefix protocol-demonstration run test:v28-mvp-qa` passes 13 tests. | implemented and tested | ## 2026-05-13 Staging Deployment Readiness Gate diff --git a/packages/btd/README.md b/packages/btd/README.md index 8f675445c..5e6e3fc2c 100644 --- a/packages/btd/README.md +++ b/packages/btd/README.md @@ -11,6 +11,9 @@ This package owns: - contributor allocation, access evaluation, ancestry review, and revenue routing - wallet-signed BTC fee receipts, ledger anchors, Exchange rights transfers, Terminal journals, reconciliation, telemetry, and upgrade receipts +- Terminal operational health reads that compose deployment lanes, telemetry, + upgrade posture, provider readiness, settlement-network posture, synthetic + testnet minting, journal rows, ledger anchors, and reconciliation state `$BTD` is not a fungible fee token. BTC pays fees. `$BTD` represents a non-fungible AssetPack share/read-right and the measured Bitcode amount in @@ -34,3 +37,10 @@ import { readBtdHoldings, } from '@bitcode/btd'; ``` + +Terminal should consume the operational-health subpath when it needs the +client-safe read model without importing storage-backed package entry points: + +```ts +import { buildTerminalOperationalHealthRead } from '@bitcode/btd/terminal-operational-health'; +``` diff --git a/packages/btd/__tests__/mocks/supabase.ts b/packages/btd/__tests__/mocks/supabase.ts new file mode 100644 index 000000000..0976a076c --- /dev/null +++ b/packages/btd/__tests__/mocks/supabase.ts @@ -0,0 +1,12 @@ +export const supabaseAdmin = { + from: jest.fn(() => { + const query = { + select: jest.fn(), + eq: jest.fn(), + maybeSingle: jest.fn().mockResolvedValue({ data: null, error: null }), + }; + query.select.mockImplementation(() => query); + query.eq.mockImplementation(() => query); + return query; + }), +}; diff --git a/packages/btd/__tests__/terminal-operational-health.test.ts b/packages/btd/__tests__/terminal-operational-health.test.ts new file mode 100644 index 000000000..d318a3c5c --- /dev/null +++ b/packages/btd/__tests__/terminal-operational-health.test.ts @@ -0,0 +1,150 @@ +import { + aggregateTerminalOperationalTelemetrySeverity, + buildTerminalOperationalHealthRead, +} from '../src/terminal-operational-health'; +import { buildV27CryptoTelemetryRecord } from '../src/telemetry'; + +const issuedAt = 'terminal-operational-health-test'; + +describe('Terminal operational health read', () => { + it('surfaces all deployment lanes and blocks value-bearing mainnet without approval root', () => { + const read = buildTerminalOperationalHealthRead({ issuedAt }); + + expect(read.lanes.map((lane) => lane.lane)).toEqual([ + 'local', + 'regtest', + 'signet', + 'testnet', + 'mainnet-ready', + 'mainnet-value-bearing', + ]); + expect(read.lanes.find((lane) => lane.lane === 'testnet')).toMatchObject({ + label: 'Public testnet', + state: 'ready', + bitcoinNetwork: 'testnet', + }); + expect(read.lanes.find((lane) => lane.lane === 'mainnet-value-bearing')).toMatchObject({ + state: 'blocked', + valueBearing: true, + operationalApprovalRoot: null, + missingEnvironmentKeys: ['BITCODE_OPERATIONAL_APPROVAL_ROOT'], + readinessReceipt: null, + }); + }); + + it('admits value-bearing mainnet only when an operational approval root is present', () => { + const read = buildTerminalOperationalHealthRead({ + issuedAt, + operationalApprovalRoots: { + 'mainnet-value-bearing': 'approval-root', + }, + }); + const mainnetValueLane = read.lanes.find((lane) => lane.lane === 'mainnet-value-bearing'); + + expect(mainnetValueLane).toMatchObject({ + state: 'ready', + operationalApprovalRoot: 'approval-root', + valueBearing: true, + }); + expect(mainnetValueLane?.readinessReceipt?.mainnetValueBearingBlocked).toBe(false); + }); + + it('aggregates telemetry into broadcaster and observer health', () => { + const telemetryRecords = [ + buildV27CryptoTelemetryRecord({ + event: 'btc_fee.broadcast_rejected', + subjectId: 'fee-1', + issuedAt, + }), + buildV27CryptoTelemetryRecord({ + event: 'ledger_provider.disagreement', + subjectId: 'anchor-1', + issuedAt, + }), + ]; + const read = buildTerminalOperationalHealthRead({ issuedAt, telemetryRecords }); + + expect(aggregateTerminalOperationalTelemetrySeverity(telemetryRecords)).toBe('critical'); + expect(read.telemetry.severity).toBe('critical'); + expect(read.broadcaster).toMatchObject({ + state: 'review', + severity: 'warning', + }); + expect(read.observer).toMatchObject({ + state: 'blocked', + severity: 'critical', + }); + }); + + it('keeps upgrade, rollback, migration, and generated type refresh visible', () => { + const read = buildTerminalOperationalHealthRead({ + issuedAt, + rollbackPlanRoot: 'rollback-root', + generatedTypeRefreshState: 'current', + }); + + expect(read.upgrade).toMatchObject({ + state: 'planned', + migrationRoot: 'terminal-migration-root', + rollbackPlanRoot: 'rollback-root', + approvalReceiptRoot: 'terminal-approval-root', + generatedTypeRefresh: { + state: 'current', + source: 'packages/orm/src/types/database.generated.ts', + }, + }); + expect(read.upgrade.receipt.kind).toBe('btd.protocol_upgrade'); + }); + + it('declares GitHub as the active VCS path and leaves broader providers future-scoped', () => { + const read = buildTerminalOperationalHealthRead({ issuedAt }); + + expect(read.providers).toEqual( + expect.arrayContaining([ + expect.objectContaining({ provider: 'github', state: 'ready' }), + expect.objectContaining({ provider: 'gitlab', state: 'future' }), + expect.objectContaining({ provider: 'bitbucket', state: 'future' }), + expect.objectContaining({ provider: 'generic-git', state: 'future' }), + ]), + ); + }); + + it('uses Bitcoin Taproot/PSBT as first-class and keeps Binance-family pilots disabled', () => { + const read = buildTerminalOperationalHealthRead({ issuedAt }); + + expect(read.settlementNetworks).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: 'bitcoin-taproot-psbt', state: 'ready' }), + expect.objectContaining({ id: 'bsc', state: 'disabled' }), + expect.objectContaining({ id: 'opbnb', state: 'disabled' }), + expect.objectContaining({ id: 'binance-web3-wallet', state: 'disabled' }), + ]), + ); + }); + + it('builds a synthetic testnet minting readback that can be ledger/database diffed', () => { + const read = buildTerminalOperationalHealthRead({ issuedAt }); + + expect(read.testnetMinting.measurementReceipt.kind).toBe('btd.measure_mint'); + expect(read.testnetMinting.measurementReceipt.tokenCount).toBe(1); + expect(read.testnetMinting.mintReceipt.kind).toBe('btd.asset_pack_mint'); + expect(read.testnetMinting.assetPackRange.tokenCount).toBe(1); + expect(read.testnetMinting.ledgerAnchor).toMatchObject({ + chain: 'bitcoin', + network: 'signet', + commitmentMethod: 'taproot', + finalityState: 'prepared', + }); + expect(read.testnetMinting.terminalJournalRows.map((row) => row.transactionKind)).toEqual([ + 'asset_pack_mint', + 'asset_pack_anchor', + ]); + expect(read.testnetMinting.terminalJournalDiff.blocking).toBe(false); + expect(read.testnetMinting.ledgerDatabaseReconciliation.blocking).toBe(false); + expect(read.testnetMinting.ledgerObservedFacts).toEqual(read.testnetMinting.databaseProjectedFacts.map((fact) => ({ + factId: fact.factId, + ledgerRoot: fact.projectedLedgerRoot, + finalityState: fact.projectedFinalityState, + }))); + }); +}); diff --git a/packages/btd/jest.config.cjs b/packages/btd/jest.config.cjs index af93d54ae..920ff715a 100644 --- a/packages/btd/jest.config.cjs +++ b/packages/btd/jest.config.cjs @@ -2,14 +2,20 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', rootDir: '.', + roots: ['/__tests__'], testMatch: ['/__tests__/**/*.test.ts'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], transform: { '^.+\\.tsx?$': [ 'ts-jest', { - tsconfig: '/../../tsconfig.json', + tsconfig: '/tsconfig.json', diagnostics: false, }, ], }, + moduleNameMapper: { + '^@bitcode/models/src/(.+)$': '/../models/src/$1', + '^@bitcode/supabase$': '/__tests__/mocks/supabase.ts', + }, }; diff --git a/packages/btd/package.json b/packages/btd/package.json index 624b0d142..0db059752 100644 --- a/packages/btd/package.json +++ b/packages/btd/package.json @@ -4,8 +4,12 @@ "private": true, "main": "src/index.ts", "types": "src/index.ts", + "exports": { + ".": "./src/index.ts", + "./terminal-operational-health": "./src/terminal-operational-health.ts" + }, "scripts": { - "test": "jest --passWithNoTests", + "test": "jest --config jest.config.cjs --runInBand", "typecheck": "tsc --noEmit --pretty false" }, "dependencies": { diff --git a/packages/btd/src/terminal-operational-health.ts b/packages/btd/src/terminal-operational-health.ts new file mode 100644 index 000000000..d895490c5 --- /dev/null +++ b/packages/btd/src/terminal-operational-health.ts @@ -0,0 +1,611 @@ +import { + buildV27CryptoDeploymentLane, + buildV27CryptoDeploymentReadinessReceipt, + V27_CRYPTO_DEPLOYMENT_ENVIRONMENT_KEYS, + type V27CryptoDeploymentLaneKind, + type V27CryptoDeploymentReadinessReceipt, +} from './deployment-lanes'; +import { + buildV27CryptoTelemetryRecord, + type V27CryptoTelemetryRecord, + type V27CryptoTelemetrySeverity, +} from './telemetry'; +import { + buildPlannedBtdProtocolUpgradeReceipt, + type BtdProtocolUpgradeReceipt, +} from './upgrade'; +import { + applyBtdMeasureMint, + createBtdMeasureMintState, + type BtdMeasureMintReceipt, +} from './measuremint'; +import { allocateAssetPackRange, type AssetPackRange } from './range'; +import { buildBtdMintReceipt, type BtdMintReceipt } from './receipts'; +import { + buildPreparedAssetPackLedgerAnchor, + type AssetPackLedgerAnchor, +} from './ledger-anchor'; +import { + buildTerminalJournalEntry, + diffTerminalJournalProjection, + type TerminalJournalDiff, + type TerminalJournalEntry, +} from './terminal-journal'; +import { + reconcileLedgerDatabaseProjection, + type DatabaseProjectedFact, + type LedgerDatabaseReconciliationReport, + type LedgerObservedFact, +} from './reconciliation'; +import { createBtdSupplyState } from './supply'; +import type { BitcoinNetwork, LedgerNetwork } from './constants'; + +export type TerminalOperationalReadinessState = + | 'ready' + | 'review' + | 'blocked' + | 'disabled' + | 'future'; + +export type TerminalOperationalHealthSeverity = V27CryptoTelemetrySeverity | 'none'; + +export interface TerminalOperationalLaneRead { + lane: V27CryptoDeploymentLaneKind; + label: string; + bitcoinNetwork: BitcoinNetwork; + ledgerNetwork: LedgerNetwork; + valueBearing: boolean; + state: TerminalOperationalReadinessState; + approvalPosture: string; + signetProofRequired: boolean; + telemetryRequired: boolean; + rollbackPlanRoot: string; + operationalApprovalRoot: string | null; + missingEnvironmentKeys: string[]; + readinessReceipt: V27CryptoDeploymentReadinessReceipt | null; +} + +export interface TerminalOperationalSubsystemRead { + id: 'btc-broadcaster' | 'ledger-observer'; + label: string; + state: Extract; + severity: TerminalOperationalHealthSeverity; + summary: string; + telemetryEventKinds: string[]; +} + +export interface TerminalOperationalUpgradeRead { + state: BtdProtocolUpgradeReceipt['upgradeState']; + migrationRoot: string; + rollbackPlanRoot: string; + approvalReceiptRoot: string; + generatedTypeRefresh: { + state: 'current' | 'pending' | 'blocked'; + source: string; + }; + receipt: BtdProtocolUpgradeReceipt; +} + +export interface TerminalOperationalProviderRead { + provider: 'github' | 'gitlab' | 'bitbucket' | 'generic-git'; + state: TerminalOperationalReadinessState; + summary: string; +} + +export interface TerminalOperationalSettlementNetworkRead { + id: 'bitcoin-taproot-psbt' | 'bsc' | 'opbnb' | 'binance-web3-wallet'; + state: TerminalOperationalReadinessState; + summary: string; +} + +export interface TerminalOperationalMintingRead { + assetPackId: string; + measurementReceipt: BtdMeasureMintReceipt; + assetPackRange: AssetPackRange; + mintReceipt: BtdMintReceipt; + terminalJournalRows: TerminalJournalEntry[]; + terminalJournalDiff: TerminalJournalDiff; + ledgerAnchor: AssetPackLedgerAnchor; + ledgerObservedFacts: LedgerObservedFact[]; + databaseProjectedFacts: DatabaseProjectedFact[]; + ledgerDatabaseReconciliation: LedgerDatabaseReconciliationReport; +} + +export interface TerminalOperationalHealthRead { + kind: 'bitcode.terminal.operational_health_read'; + lanes: TerminalOperationalLaneRead[]; + telemetry: { + severity: TerminalOperationalHealthSeverity; + records: V27CryptoTelemetryRecord[]; + }; + broadcaster: TerminalOperationalSubsystemRead; + observer: TerminalOperationalSubsystemRead; + upgrade: TerminalOperationalUpgradeRead; + providers: TerminalOperationalProviderRead[]; + settlementNetworks: TerminalOperationalSettlementNetworkRead[]; + testnetMinting: TerminalOperationalMintingRead; + sourceBasis: string[]; +} + +export interface BuildTerminalOperationalHealthReadInput { + issuedAt?: string; + rollbackPlanRoot?: string; + operationalApprovalRoots?: Partial>; + presentEnvironmentKeysByLane?: Partial>; + telemetryRecords?: V27CryptoTelemetryRecord[]; + upgradeReceipt?: BtdProtocolUpgradeReceipt; + generatedTypeRefreshState?: TerminalOperationalUpgradeRead['generatedTypeRefresh']['state']; +} + +const DEFAULT_ISSUED_AT = 'terminal-operational-health-read'; +const DEFAULT_ROLLBACK_PLAN_ROOT = 'terminal-rollback-plan-root'; + +const LANE_CONFIG: Record< + V27CryptoDeploymentLaneKind, + { + label: string; + bitcoinNetwork: BitcoinNetwork; + ledgerNetwork: LedgerNetwork; + requiredEnvironmentKeys: string[]; + } +> = { + local: { + label: 'Local', + bitcoinNetwork: 'regtest', + ledgerNetwork: 'local', + requiredEnvironmentKeys: ['BITCODE_CRYPTO_LANE', 'BITCODE_ROLLBACK_PLAN_ROOT'], + }, + regtest: { + label: 'Regtest', + bitcoinNetwork: 'regtest', + ledgerNetwork: 'regtest', + requiredEnvironmentKeys: [...V27_CRYPTO_DEPLOYMENT_ENVIRONMENT_KEYS], + }, + signet: { + label: 'Signet', + bitcoinNetwork: 'signet', + ledgerNetwork: 'signet', + requiredEnvironmentKeys: [...V27_CRYPTO_DEPLOYMENT_ENVIRONMENT_KEYS], + }, + testnet: { + label: 'Public testnet', + bitcoinNetwork: 'testnet', + ledgerNetwork: 'testnet', + requiredEnvironmentKeys: [...V27_CRYPTO_DEPLOYMENT_ENVIRONMENT_KEYS], + }, + 'mainnet-ready': { + label: 'Mainnet ready', + bitcoinNetwork: 'mainnet', + ledgerNetwork: 'mainnet', + requiredEnvironmentKeys: [...V27_CRYPTO_DEPLOYMENT_ENVIRONMENT_KEYS], + }, + 'mainnet-value-bearing': { + label: 'Mainnet value bearing', + bitcoinNetwork: 'mainnet', + ledgerNetwork: 'mainnet', + requiredEnvironmentKeys: [ + ...V27_CRYPTO_DEPLOYMENT_ENVIRONMENT_KEYS, + 'BITCODE_OPERATIONAL_APPROVAL_ROOT', + ], + }, +}; + +const LANE_ORDER = Object.keys(LANE_CONFIG) as V27CryptoDeploymentLaneKind[]; + +const SEVERITY_RANK: Record = { + none: 0, + info: 1, + warning: 2, + critical: 3, +}; + +export function buildTerminalOperationalHealthRead( + input: BuildTerminalOperationalHealthReadInput = {}, +): TerminalOperationalHealthRead { + const issuedAt = input.issuedAt ?? DEFAULT_ISSUED_AT; + const rollbackPlanRoot = input.rollbackPlanRoot ?? DEFAULT_ROLLBACK_PLAN_ROOT; + const telemetryRecords = + input.telemetryRecords ?? + [ + buildV27CryptoTelemetryRecord({ + event: 'database_projection.repaired', + subjectId: 'terminal-operational-health', + receiptRoot: 'terminal-operational-health-receipt-root', + issuedAt, + }), + ]; + + const lanes = LANE_ORDER.map((lane) => + buildTerminalOperationalLaneRead({ + lane, + issuedAt, + rollbackPlanRoot, + operationalApprovalRoot: input.operationalApprovalRoots?.[lane], + presentEnvironmentKeys: input.presentEnvironmentKeysByLane?.[lane], + }), + ); + + return { + kind: 'bitcode.terminal.operational_health_read', + lanes, + telemetry: { + severity: aggregateTerminalOperationalTelemetrySeverity(telemetryRecords), + records: telemetryRecords, + }, + broadcaster: buildTerminalOperationalSubsystemRead({ + id: 'btc-broadcaster', + records: telemetryRecords, + }), + observer: buildTerminalOperationalSubsystemRead({ + id: 'ledger-observer', + records: telemetryRecords, + }), + upgrade: buildTerminalOperationalUpgradeRead({ + issuedAt, + rollbackPlanRoot, + upgradeReceipt: input.upgradeReceipt, + generatedTypeRefreshState: input.generatedTypeRefreshState, + }), + providers: buildTerminalOperationalProviderReads(), + settlementNetworks: buildTerminalOperationalSettlementNetworkReads(), + testnetMinting: buildTerminalOperationalMintingRead({ issuedAt }), + sourceBasis: [ + 'packages/btd/src/deployment-lanes.ts', + 'packages/btd/src/telemetry.ts', + 'packages/btd/src/upgrade.ts', + 'packages/btd/src/measuremint.ts', + 'packages/btd/src/range.ts', + 'packages/btd/src/receipts.ts', + 'packages/btd/src/terminal-journal.ts', + 'packages/btd/src/ledger-anchor.ts', + 'packages/btd/src/reconciliation.ts', + ], + }; +} + +export function aggregateTerminalOperationalTelemetrySeverity( + records: V27CryptoTelemetryRecord[], +): TerminalOperationalHealthSeverity { + return records.reduce( + (current, record) => + SEVERITY_RANK[record.severity] > SEVERITY_RANK[current] ? record.severity : current, + 'none', + ); +} + +function buildTerminalOperationalLaneRead(input: { + lane: V27CryptoDeploymentLaneKind; + issuedAt: string; + rollbackPlanRoot: string; + operationalApprovalRoot?: string; + presentEnvironmentKeys?: string[]; +}): TerminalOperationalLaneRead { + const config = LANE_CONFIG[input.lane]; + const presentEnvironmentKeys = input.presentEnvironmentKeys ?? config.requiredEnvironmentKeys; + const operationalApprovalRoot = input.operationalApprovalRoot ?? null; + + if (input.lane === 'mainnet-value-bearing' && !operationalApprovalRoot) { + return { + lane: input.lane, + label: config.label, + bitcoinNetwork: config.bitcoinNetwork, + ledgerNetwork: config.ledgerNetwork, + valueBearing: true, + state: 'blocked', + approvalPosture: 'Blocked until an operational approval root is present.', + signetProofRequired: true, + telemetryRequired: true, + rollbackPlanRoot: input.rollbackPlanRoot, + operationalApprovalRoot, + missingEnvironmentKeys: ['BITCODE_OPERATIONAL_APPROVAL_ROOT'], + readinessReceipt: null, + }; + } + + const deploymentLane = buildV27CryptoDeploymentLane({ + lane: input.lane, + bitcoinNetwork: config.bitcoinNetwork, + ledgerNetwork: config.ledgerNetwork, + rollbackPlanRoot: input.rollbackPlanRoot, + operationalApprovalRoot: operationalApprovalRoot ?? undefined, + }); + const readinessReceipt = buildV27CryptoDeploymentReadinessReceipt({ + readinessId: `terminal:${input.lane}:readiness`, + lane: deploymentLane, + presentEnvironmentKeys, + requiredEnvironmentKeys: config.requiredEnvironmentKeys, + issuedAt: input.issuedAt, + }); + + return { + lane: input.lane, + label: config.label, + bitcoinNetwork: config.bitcoinNetwork, + ledgerNetwork: config.ledgerNetwork, + valueBearing: deploymentLane.valueBearing, + state: readinessReceipt.blocking ? 'blocked' : 'ready', + approvalPosture: describeLaneApprovalPosture(input.lane, operationalApprovalRoot), + signetProofRequired: deploymentLane.signetProofRequired, + telemetryRequired: deploymentLane.telemetryRequired, + rollbackPlanRoot: deploymentLane.rollbackPlanRoot, + operationalApprovalRoot, + missingEnvironmentKeys: readinessReceipt.missingEnvironmentKeys, + readinessReceipt, + }; +} + +function describeLaneApprovalPosture( + lane: V27CryptoDeploymentLaneKind, + operationalApprovalRoot: string | null, +): string { + if (lane === 'mainnet-value-bearing') { + return operationalApprovalRoot + ? 'Operational approval root present for value-bearing mainnet.' + : 'Blocked until an operational approval root is present.'; + } + + if (lane === 'mainnet-ready') { + return 'Non-value mainnet readiness lane; signet proof and rollback roots remain required.'; + } + + if (lane === 'signet') { + return 'Signet proof lane; no value-bearing mainnet settlement.'; + } + + return 'Non-value lane; no operational approval root required.'; +} + +function buildTerminalOperationalSubsystemRead(input: { + id: TerminalOperationalSubsystemRead['id']; + records: V27CryptoTelemetryRecord[]; +}): TerminalOperationalSubsystemRead { + const eventKinds = + input.id === 'btc-broadcaster' + ? [ + 'btc_fee.transaction_construction_failed', + 'btc_fee.broadcast_rejected', + 'btc_fee.confirmation_lag', + 'btc_fee.replaced', + ] + : ['ledger_anchor.reorged', 'ledger_anchor.failed', 'ledger_provider.disagreement']; + const matchingRecords = input.records.filter((record) => eventKinds.includes(record.event)); + const severity = aggregateTerminalOperationalTelemetrySeverity(matchingRecords); + const state = + severity === 'critical' ? 'blocked' : severity === 'warning' ? 'review' : 'ready'; + + return { + id: input.id, + label: input.id === 'btc-broadcaster' ? 'BTC broadcaster' : 'Ledger observer', + state, + severity, + summary: + input.id === 'btc-broadcaster' + ? describeBroadcasterSummary(state) + : describeObserverSummary(state), + telemetryEventKinds: eventKinds, + }; +} + +function describeBroadcasterSummary(state: TerminalOperationalSubsystemRead['state']): string { + if (state === 'blocked') { + return 'Broadcast telemetry is critical; signing and fee transaction broadcast cannot be treated as ready.'; + } + if (state === 'review') { + return 'Broadcast telemetry requires operator review before value-bearing settlement.'; + } + return 'Broadcast path is ready for PSBT construction, signing handoff, and transaction submission.'; +} + +function describeObserverSummary(state: TerminalOperationalSubsystemRead['state']): string { + if (state === 'blocked') { + return 'Ledger observation is critical; anchors or provider agreement must be repaired before promotion.'; + } + if (state === 'review') { + return 'Ledger observation requires review before promotion.'; + } + return 'Ledger observation is ready for anchor finality and projection comparison.'; +} + +function buildTerminalOperationalUpgradeRead(input: { + issuedAt: string; + rollbackPlanRoot: string; + upgradeReceipt?: BtdProtocolUpgradeReceipt; + generatedTypeRefreshState?: TerminalOperationalUpgradeRead['generatedTypeRefresh']['state']; +}): TerminalOperationalUpgradeRead { + const receipt = + input.upgradeReceipt ?? + buildPlannedBtdProtocolUpgradeReceipt({ + upgradeId: 'terminal-upgrade-readiness', + fromVersion: 'active-canon', + toVersion: 'draft-target', + network: 'signet', + migrationRoot: 'terminal-migration-root', + preStateRoot: 'terminal-pre-state-root', + approvalReceiptRoot: 'terminal-approval-root', + rollbackPlanRoot: input.rollbackPlanRoot, + issuedAt: input.issuedAt, + }); + + return { + state: receipt.upgradeState, + migrationRoot: receipt.migrationRoot, + rollbackPlanRoot: receipt.rollbackPlanRoot, + approvalReceiptRoot: receipt.approvalReceiptRoot, + generatedTypeRefresh: { + state: input.generatedTypeRefreshState ?? 'pending', + source: 'packages/orm/src/types/database.generated.ts', + }, + receipt, + }; +} + +function buildTerminalOperationalProviderReads(): TerminalOperationalProviderRead[] { + return [ + { + provider: 'github', + state: 'ready', + summary: 'GitHub repository selection, branch, commit, and PR delivery are the active VCS path.', + }, + { + provider: 'gitlab', + state: 'future', + summary: 'GitLab is not a launch VCS path for Terminal Reading.', + }, + { + provider: 'bitbucket', + state: 'future', + summary: 'Bitbucket is not a launch VCS path for Terminal Reading.', + }, + { + provider: 'generic-git', + state: 'future', + summary: 'Generic Git providers remain outside the current Terminal Reading admission path.', + }, + ]; +} + +function buildTerminalOperationalSettlementNetworkReads(): TerminalOperationalSettlementNetworkRead[] { + return [ + { + id: 'bitcoin-taproot-psbt', + state: 'ready', + summary: 'Bitcoin Taproot commitments and PSBT signing are the first-class settlement path.', + }, + { + id: 'bsc', + state: 'disabled', + summary: 'BSC settlement pilots are disabled until a later Protocol gate explicitly admits them.', + }, + { + id: 'opbnb', + state: 'disabled', + summary: 'opBNB settlement pilots are disabled until a later Protocol gate explicitly admits them.', + }, + { + id: 'binance-web3-wallet', + state: 'disabled', + summary: 'Binance Web3 wallet pilots are disabled until a later Protocol gate explicitly admits them.', + }, + ]; +} + +function buildTerminalOperationalMintingRead(input: { + issuedAt: string; +}): TerminalOperationalMintingRead { + const assetPackId = 'terminal-testnet-asset-pack'; + const measurement = applyBtdMeasureMint({ + state: createBtdMeasureMintState(), + assetPackId, + semanticVolume: { normalizedBitcodeVolume: 2n }, + proofRoot: 'terminal-measurement-proof-root', + settlementJournalRoot: 'terminal-settlement-journal-root', + accessPolicyHash: 'terminal-access-policy-hash', + exchangeSequence: 1n, + issuedAt: input.issuedAt, + }); + const allocation = allocateAssetPackRange( + createBtdSupplyState(), + { + assetPackId, + readId: 'terminal-read', + acceptedNeed: true, + acceptedFit: true, + sourceManifestRoot: 'terminal-source-manifest-root', + measurementReceiptRoot: measurement.receipt.proofRoot, + fitReceiptRoot: 'terminal-fit-receipt-root', + proofRoot: 'terminal-mint-proof-root', + dedupeReceiptRoot: 'terminal-dedupe-receipt-root', + settlementJournalRoot: 'terminal-settlement-journal-root', + exchangeReceiptRoot: 'terminal-exchange-receipt-root', + accessPolicyId: 'terminal-access-policy', + accessPolicyHash: 'terminal-access-policy-hash', + normalizedBitcodeVolume: measurement.receipt.normalizedBitcodeVolume, + tokenCount: Math.max(1, measurement.receipt.tokenCount), + mintedAtExchangeSequence: 2n, + }, + ); + const mintReceipt = buildBtdMintReceipt(allocation, input.issuedAt); + const ledgerAnchor = buildPreparedAssetPackLedgerAnchor({ + anchorId: 'terminal-testnet-anchor', + assetPackId, + network: 'signet', + commitmentMethod: 'taproot', + commitmentRoot: 'terminal-anchor-commitment-root', + sourceManifestRoot: mintReceipt.sourceManifestRoot, + proofRoot: mintReceipt.proofRoot, + accessPolicyHash: mintReceipt.accessPolicyHash, + btdRangeStart: mintReceipt.rangeStart, + btdRangeEndExclusive: mintReceipt.rangeEndExclusive, + }); + const terminalJournalRows = [ + buildTerminalJournalEntry({ + journalEntryId: 'terminal-testnet-journal-mint', + transactionKind: 'asset_pack_mint', + actorId: 'terminal-operator', + preStateRoot: 'terminal-pre-mint-state-root', + postStateRoot: 'terminal-post-mint-state-root', + receiptRoots: [mintReceipt.proofRoot, mintReceipt.measurementReceiptRoot], + ledgerAnchorIds: [ledgerAnchor.anchorId], + exchangeSequence: 3n, + issuedAt: input.issuedAt, + }), + buildTerminalJournalEntry({ + journalEntryId: 'terminal-testnet-journal-anchor', + transactionKind: 'asset_pack_anchor', + actorId: 'terminal-operator', + preStateRoot: 'terminal-post-mint-state-root', + postStateRoot: 'terminal-post-anchor-state-root', + receiptRoots: [ledgerAnchor.commitmentRoot], + ledgerAnchorIds: [ledgerAnchor.anchorId], + exchangeSequence: 4n, + issuedAt: input.issuedAt, + }), + ]; + const terminalJournalDiff = diffTerminalJournalProjection(terminalJournalRows[0], { + journalEntryId: terminalJournalRows[0].journalEntryId, + postStateRoot: terminalJournalRows[0].postStateRoot, + receiptRoots: terminalJournalRows[0].receiptRoots, + ledgerAnchorIds: terminalJournalRows[0].ledgerAnchorIds, + }); + const ledgerObservedFacts: LedgerObservedFact[] = [ + { + factId: ledgerAnchor.anchorId, + ledgerRoot: ledgerAnchor.commitmentRoot, + finalityState: ledgerAnchor.finalityState, + }, + ]; + const databaseProjectedFacts: DatabaseProjectedFact[] = [ + { + factId: ledgerAnchor.anchorId, + projectedLedgerRoot: ledgerAnchor.commitmentRoot, + projectedFinalityState: ledgerAnchor.finalityState, + }, + ]; + + return { + assetPackId, + measurementReceipt: measurement.receipt, + assetPackRange: allocation.range, + mintReceipt, + terminalJournalRows, + terminalJournalDiff, + ledgerAnchor, + ledgerObservedFacts, + databaseProjectedFacts, + ledgerDatabaseReconciliation: reconcileLedgerDatabaseProjection({ + reconciliationId: 'terminal-testnet-reconciliation', + ledgerFacts: ledgerObservedFacts, + databaseFacts: databaseProjectedFacts, + metaphysicalFacts: [ + { + factId: 'terminal-need-fit-context', + factKind: 'need_fit_context', + canonicalRoot: 'terminal-private-need-fit-root', + receiptRoot: mintReceipt.fitReceiptRoot, + private: true, + }, + ], + issuedAt: input.issuedAt, + }), + }; +} diff --git a/packages/pipeline-hosts/README.md b/packages/pipeline-hosts/README.md index 6b5fb1490..bd43ad4aa 100644 --- a/packages/pipeline-hosts/README.md +++ b/packages/pipeline-hosts/README.md @@ -45,14 +45,14 @@ so historical deposited source revisions do not need to carry newer harness dependencies. The harness manifest includes the staged Reading boundary. The active stage -sequence begins with Need synthesis, Need review, and Need-Fit search before -candidate ranking and AssetPack synthesis. The live runner synthesizes a +sequence begins with Need synthesis, Need review, and Finding Fits discovery +before fit deposit ranking and AssetPack synthesis. The live runner synthesizes a typed `bitcode.read.need` object from the Read request, source revision, and Deposit context, accepts it for the current harness invocation, and passes it to the AssetPack pipeline as `acceptedReadNeed` with `requireAcceptedReadNeed=true`. Product routes that already have a user-reviewed -Need should pass that accepted object directly; if strict Need-Fit execution is -requested without an accepted Need, candidate recall must return +Need should pass that accepted object directly; if strict Finding Fits run is +requested without an accepted Need, depository discovery must return `blocked_readiness` before searching the depository. Structured database telemetry is part of the harness contract. A real Read/Fit @@ -62,7 +62,7 @@ pipeline run must write the deliverable hierarchy: `deliverable_pipeline_agent_steps`, `deliverable_pipeline_generations`, and `deliverable_pipeline_tool_executions`. Generation rows should retain the interpolated model messages when available, raw and parsed output, model -identity, token usage, and phase/agent/step context. +identity, token usage, and phase/agent/PTRR step context. Tool rows are required when PTRR agents request evidence tools. The AssetPack pipeline registers `bitcode.asset-pack.verification` as an evidence-only verification readback tool for risk-admission and readiness work; a live @@ -73,11 +73,11 @@ agent asked and what the tool returned. Stored `tools/*` status results from finish delivery are tool executions too; branch creation, file writes, and pull-request creation must land in the same structured table as explicit `tool-use` events. Artifact telemetry must preserve tool name, ok/error state, -and input/output/error presence so the execution log can identify each -phase-agent-step tool invocation. +and input/output/error presence so the log can identify each +phase-agent-PTRR-step tool invocation. Failed runs must still export `evidence.json` with the execution tree and last-known stream events so staging operators can see which SDIVF phase, PTRR -agent, generation, or tool last produced input/output evidence. +agent, ThricifiedGeneration, or tool last produced input/output evidence. The runner subscribes to stream events for artifact telemetry even when database streaming is disabled; database streaming adds persistence, not basic event visibility. diff --git a/packages/pipeline-hosts/src/__tests__/asset-pack-harness.test.ts b/packages/pipeline-hosts/src/__tests__/asset-pack-harness.test.ts index 9a2301dea..908093e8b 100644 --- a/packages/pipeline-hosts/src/__tests__/asset-pack-harness.test.ts +++ b/packages/pipeline-hosts/src/__tests__/asset-pack-harness.test.ts @@ -180,6 +180,11 @@ describe('asset-pack sandbox harness plan', () => { expect(source).toContain('toolInputPresent'); expect(source).toContain('toolOutputPresent'); expect(source).toContain('toolErrorPresent'); + expect(source).toContain('promptTemplatePresent'); + expect(source).toContain('interpolatedPromptPresent'); + expect(source).toContain('rawModelResponsePresent'); + expect(source).toContain('parsedTypedOutputPresent'); + expect(source).toContain('inferenceAudit'); expect(source).toContain('Pipeline produced '); expect(source).toContain('sourceSafePreview,'); expect(source).toContain("execution.store('asset-pack/preview', 'feeQuote'"); diff --git a/packages/pipeline-hosts/src/asset-pack-harness.ts b/packages/pipeline-hosts/src/asset-pack-harness.ts index 8021a606c..eb25acbc7 100644 --- a/packages/pipeline-hosts/src/asset-pack-harness.ts +++ b/packages/pipeline-hosts/src/asset-pack-harness.ts @@ -522,6 +522,16 @@ function summarizeStreamEvent(event) { const data = event?.data && typeof event.data === 'object' && !Array.isArray(event.data) ? event.data : null; + const llmAudit = event?.namespace === 'llm' && data + ? { + promptTemplate: summarizeInspectableValue(data.promptTemplate ?? null), + interpolatedPrompt: summarizeInspectableValue(data.interpolatedPrompt ?? data.messages ?? null), + reasoning: summarizeInspectableValue(data.reasoning ?? null), + judgment: summarizeInspectableValue(data.judgment ?? null), + rawModelResponse: summarizeInspectableValue(data.rawResponse ?? data.content ?? null), + parsedTypedOutput: summarizeInspectableValue(data.parsedTypedOutput ?? data.parsed ?? null), + } + : null; return { type: 'pipeline-stream-event', stage: stageForStreamEvent(event), @@ -540,6 +550,13 @@ function summarizeStreamEvent(event) { inputMessageCount: Array.isArray(data?.messages) ? data.messages.length : null, outputContentLength: typeof data?.content === 'string' ? data.content.length : null, parsedOutputPresent: Boolean(data?.parsed), + promptTemplatePresent: Boolean(data?.promptTemplate), + interpolatedPromptPresent: Boolean(data?.interpolatedPrompt || data?.messages), + reasoningPresent: Boolean(data?.reasoning), + judgmentPresent: Boolean(data?.judgment), + rawModelResponsePresent: Boolean(data?.rawResponse || data?.content), + parsedTypedOutputPresent: Boolean(data?.parsedTypedOutput || data?.parsed), + inferenceAudit: llmAudit, inspectable: summarizeLlmInspectable(event, event?.data ?? null), }; } diff --git a/packages/pipelines/asset-pack/README.md b/packages/pipelines/asset-pack/README.md index af7fd15c0..817501b39 100644 --- a/packages/pipelines/asset-pack/README.md +++ b/packages/pipelines/asset-pack/README.md @@ -28,7 +28,7 @@ configured limit before Finish. AssetPackPipeline (SDIVF with DIV iteration) ├── Preprocess │ ├── Read and source-revision normalization -│ ├── Depository candidate recall +│ ├── Depository Finding Fits discovery │ ├── Source-bound ranking │ └── Fit result-state evidence ├── Setup diff --git a/packages/pipelines/asset-pack/package.json b/packages/pipelines/asset-pack/package.json index e5e704aa2..2d8f7f24f 100644 --- a/packages/pipelines/asset-pack/package.json +++ b/packages/pipelines/asset-pack/package.json @@ -4,6 +4,12 @@ "description": "Bitcode AssetPack pipeline for read-satisfying written-asset synthesis with Finish/Delivering delivery mechanisms", "main": "src/index.ts", "types": "src/index.ts", + "exports": { + ".": "./src/index.ts", + "./read-need": "./src/read-need.ts", + "./reading-pipeline-contract": "./src/reading-pipeline-contract.ts", + "./depository-search": "./src/depository-search.ts" + }, "scripts": { "build": "tsc --noEmit", "test": "pnpm exec jest --config jest.config.cjs --passWithNoTests", diff --git a/packages/pipelines/asset-pack/src/__tests__/asset-pack-synthesize-artifacts-agent.test.ts b/packages/pipelines/asset-pack/src/__tests__/asset-pack-synthesize-artifacts-agent.test.ts index 1e641f226..f3e3e56d1 100644 --- a/packages/pipelines/asset-pack/src/__tests__/asset-pack-synthesize-artifacts-agent.test.ts +++ b/packages/pipelines/asset-pack/src/__tests__/asset-pack-synthesize-artifacts-agent.test.ts @@ -26,10 +26,10 @@ describe('assetPackSynthesizeArtifacts', () => { expect(result.assetPack.read).toBe('Read the deposited source revision for terminal closure.'); expect(result.assetPackSynthesisArtifacts.summary).toContain('engineeredsoftware/ENGI@main@abc123'); - expect(result.assetPackSynthesisArtifacts.summary).toContain('worthy_fit with 1 selected candidate'); + expect(result.assetPackSynthesisArtifacts.summary).toContain('worthy_fit with 1 qualifying fit deposit'); expect(result.assetPackSynthesisArtifacts.proofEvidence).toEqual( expect.arrayContaining([ - 'Selected candidate assets: deposit-1', + 'Fit deposit assets: deposit-1', 'Query root: sha256:query', 'Ranking root: sha256:ranking', 'Embedding policy: openai text-embedding-3-small', diff --git a/packages/pipelines/asset-pack/src/__tests__/bounded-structured-inference.test.ts b/packages/pipelines/asset-pack/src/__tests__/bounded-structured-inference.test.ts index 5172ab5d3..71d75bb88 100644 --- a/packages/pipelines/asset-pack/src/__tests__/bounded-structured-inference.test.ts +++ b/packages/pipelines/asset-pack/src/__tests__/bounded-structured-inference.test.ts @@ -55,6 +55,11 @@ describe('runBoundedStructuredInference', () => { step: 'bounded', systemPrompt: 'Return JSON.', userPrompt: 'Return a result.', + promptTemplate: { + templateId: 'test.prompt', + system: 'Return JSON.', + user: 'Return {{result}}.', + }, schema: ResultSchema, fallback: () => ({ summary: 'fallback', accepted: false }), execution, @@ -63,11 +68,24 @@ describe('runBoundedStructuredInference', () => { expect(result).toEqual({ summary: 'fallback', accepted: false }); expect(stores).toEqual( expect.arrayContaining([ + expect.objectContaining({ + namespace: 'llm', + key: 'input', + value: expect.objectContaining({ + promptTemplate: expect.objectContaining({ templateId: 'test.prompt' }), + interpolatedPrompt: expect.objectContaining({ user: 'Return a result.' }), + }), + }), expect.objectContaining({ namespace: 'bounded-inference', key: 'status', value: 'fallback-no-llm', }), + expect.objectContaining({ + namespace: 'bounded-inference', + key: 'mode', + value: 'thricified-generation', + }), ]) ); }); @@ -84,6 +102,11 @@ describe('runBoundedStructuredInference', () => { step: 'bounded', systemPrompt: 'Return JSON.', userPrompt: 'Return a result.', + promptTemplate: { + templateId: 'test.prompt', + system: 'Return JSON.', + user: 'Return {{result}}.', + }, schema: ResultSchema, fallback: () => ({ summary: 'fallback', accepted: false }), execution, @@ -104,11 +127,33 @@ describe('runBoundedStructuredInference', () => { it('uses the execution LLM for real bounded inference and stores parsed output telemetry', async () => { process.env.BITCODE_ASSET_PACK_REAL_INFERENCE = '1'; process.env.BITCODE_ASSET_PACK_REAL_INFERENCE_PROFILE = 'bounded'; - const llm = jest.fn(async () => ({ - content: JSON.stringify({ summary: 'model result', accepted: true }), - usage: { inputTokens: 12, outputTokens: 8, totalTokens: 20 }, - metadata: { provider: 'test-provider', model: 'test-model' }, - })); + const llm = jest + .fn() + .mockResolvedValueOnce({ + content: JSON.stringify({ + analysis: 'Need is precise enough to answer.', + steps: ['Read context', 'Plan typed output'], + conclusion: 'Return the typed result.', + confidence: 0.91, + }), + usage: { inputTokens: 12, outputTokens: 8, totalTokens: 20 }, + metadata: { provider: 'test-provider', model: 'test-model' }, + }) + .mockResolvedValueOnce({ + content: JSON.stringify({ + quality: 0.9, + issues: [], + suggestions: [], + approved: true, + }), + usage: { inputTokens: 9, outputTokens: 5, totalTokens: 14 }, + metadata: { provider: 'test-provider', model: 'test-model' }, + }) + .mockResolvedValueOnce({ + content: JSON.stringify({ summary: 'model result', accepted: true }), + usage: { inputTokens: 7, outputTokens: 4, totalTokens: 11 }, + metadata: { provider: 'test-provider', model: 'test-model' }, + }); const { execution, stores } = makeExecution(llm); const result = await runBoundedStructuredInference({ @@ -117,13 +162,19 @@ describe('runBoundedStructuredInference', () => { step: 'bounded', systemPrompt: 'Return JSON.', userPrompt: 'Return a result.', + promptTemplate: { + templateId: 'test.prompt', + system: 'Return JSON.', + user: 'Return {{result}}.', + }, schema: ResultSchema, fallback: () => ({ summary: 'fallback', accepted: false }), execution, }); expect(result).toEqual({ summary: 'model result', accepted: true }); - expect(llm).toHaveBeenCalledWith( + expect(llm).toHaveBeenCalledTimes(3); + expect(llm).toHaveBeenLastCalledWith( expect.objectContaining({ config: expect.objectContaining({ responseFormat: 'json', @@ -137,9 +188,40 @@ describe('runBoundedStructuredInference', () => { key: 'status', value: 'success', }), + expect.objectContaining({ + namespace: 'bounded-inference', + key: 'mode', + value: 'thricified-generation', + }), + expect.objectContaining({ + namespace: 'llm', + key: 'reasoningOutput', + value: expect.objectContaining({ + parsedTypedOutput: expect.objectContaining({ conclusion: 'Return the typed result.' }), + }), + }), + expect.objectContaining({ + namespace: 'llm', + key: 'judgmentOutput', + value: expect.objectContaining({ + parsedTypedOutput: expect.objectContaining({ approved: true }), + }), + }), expect.objectContaining({ namespace: 'llm', key: 'parsedOutput', + value: expect.objectContaining({ + parsedTypedOutput: { summary: 'model result', accepted: true }, + reasoning: expect.objectContaining({ confidence: 0.91 }), + judgment: expect.objectContaining({ approved: true }), + }), + }), + expect.objectContaining({ + namespace: 'llm', + key: 'output', + value: expect.objectContaining({ + rawResponse: JSON.stringify({ summary: 'model result', accepted: true }), + }), }), ]) ); diff --git a/packages/pipelines/asset-pack/src/__tests__/depository-search.test.ts b/packages/pipelines/asset-pack/src/__tests__/depository-search.test.ts index 324cdc2dc..e9fcf6fff 100644 --- a/packages/pipelines/asset-pack/src/__tests__/depository-search.test.ts +++ b/packages/pipelines/asset-pack/src/__tests__/depository-search.test.ts @@ -86,6 +86,10 @@ describe('AssetPack depository search', () => { }); expect(result.resultState).toBe('worthy_fit'); + expect(result.fitDepositAssetIds).toEqual([ + 'asset_repository-revision-deposit-engineeredsoftware-engi', + ]); + expect(result.fitDeposits).toHaveLength(1); expect(result.selectedCandidateAssetIds).toEqual([ 'asset_repository-revision-deposit-engineeredsoftware-engi', ]); @@ -108,6 +112,33 @@ describe('AssetPack depository search', () => { expect(result.rankingRoot).toMatch(/^sha256:/); }); + it('discovers every qualifying fit deposit above the configured thresholds for implementation context', async () => { + const result = await searchDepositoryAssetSpace({ + read, + assets: [ + asset({ assetId: 'fit-deposit-1', title: 'Terminal path deposit one' }), + asset({ + assetId: 'fit-deposit-2', + title: 'Terminal path deposit two', + contentRoot: 'sha256:test-content-root-two', + contentUnits: [ + { + unitId: 'fit-deposit-2:unit-1', + unitKind: 'repository-revision', + text: + 'Deposit Read Fit AssetPack evidence proof-root finality readback and Supabase ledger reconciliation for Terminal.', + }, + ], + }), + ], + }); + + expect(result.resultState).toBe('worthy_fit'); + expect(result.fitDepositAssetIds).toEqual(['fit-deposit-1', 'fit-deposit-2']); + expect(result.fitDeposits.map((fit) => fit.assetId)).toEqual(result.fitDepositAssetIds); + expect(result.selectedCandidateAssetIds).toEqual(result.fitDepositAssetIds); + }); + it('blocks readiness when candidate search finds source evidence without proof', async () => { const result = await searchDepositoryAssetSpace({ read, @@ -254,6 +285,12 @@ describe('AssetPack depository search', () => { expect(findStored(exec, 'depository/search', 'result')?.resultState).toBe('worthy_fit'); expect(findStored(exec, 'depository/search', 'embeddingPolicy')?.dimensions).toBe(1536); expect(output.fitResult.resultState).toBe('worthy_fit'); + expect(output.fitResult.fitDepositAssetIds).toEqual([ + 'asset_repository-revision-deposit-engineeredsoftware-engi', + ]); + expect(output.fitResult.selectionTrace.fitDeposits[0]).toMatchObject({ + assetId: 'asset_repository-revision-deposit-engineeredsoftware-engi', + }); expect(output.fitResult.embeddingPolicy.model).toBe('text-embedding-3-small'); expect(['settlement-eligible', 'patch-eligible']).toContain( output.fitResult.selectionTrace.selectedCandidates[0].useTier @@ -291,5 +328,8 @@ describe('AssetPack depository search', () => { expect(output.depositorySearch.selectedCandidateAssetIds).toEqual([ 'asset_repository-revision-deposit-engineeredsoftware-engi', ]); + expect(output.depositorySearch.fitDepositAssetIds).toEqual([ + 'asset_repository-revision-deposit-engineeredsoftware-engi', + ]); }); }); diff --git a/packages/pipelines/asset-pack/src/__tests__/read-need.test.ts b/packages/pipelines/asset-pack/src/__tests__/read-need.test.ts index d06d6e812..d2759375f 100644 --- a/packages/pipelines/asset-pack/src/__tests__/read-need.test.ts +++ b/packages/pipelines/asset-pack/src/__tests__/read-need.test.ts @@ -1,11 +1,12 @@ import { acceptReadNeed, - admitNeedFitSearch, + admitReadFindingFits, buildAssetPackSourceSafePreview, buildShareToFeePreview, isAcceptedReadNeed, readNeedToDepositorySearchRead, resolveAssetPackReadRightState, + synthesizeReadNeedForPipelineInputWithInference, synthesizeReadNeedForPipelineInput, } from '../read-need'; import { @@ -53,7 +54,17 @@ function depositoryAsset() { }; } -describe('Read-Need synthesis and Need-Fit admission', () => { +describe('Read-Need synthesis and Finding Fits admission', () => { + const originalRealInferenceFlag = process.env.BITCODE_ASSET_PACK_REAL_INFERENCE; + + afterEach(() => { + if (originalRealInferenceFlag === undefined) { + delete process.env.BITCODE_ASSET_PACK_REAL_INFERENCE; + } else { + process.env.BITCODE_ASSET_PACK_REAL_INFERENCE = originalRealInferenceFlag; + } + }); + it('synthesizes a measured Need that remains pending until accepted', () => { const need = synthesizeReadNeedForPipelineInput(input); @@ -75,11 +86,11 @@ describe('Read-Need synthesis and Need-Fit admission', () => { ); }); - it('accepts a Need as the only admissible input to strict Need-Fit search', () => { + it('accepts a Need as the only admissible input to strict Finding Fits search', () => { const accepted = acceptReadNeed(synthesizeReadNeedForPipelineInput(input), '2026-05-18T00:00:00.000Z'); expect(isAcceptedReadNeed(accepted)).toBe(true); - expect(admitNeedFitSearch({ acceptedReadNeed: accepted, requireAcceptedReadNeed: true })).toMatchObject({ + expect(admitReadFindingFits({ acceptedReadNeed: accepted, requireAcceptedReadNeed: true })).toMatchObject({ admitted: true, acceptedNeed: accepted, blockers: [], @@ -91,7 +102,66 @@ describe('Read-Need synthesis and Need-Fit admission', () => { }); }); - it('blocks strict Need-Fit search before Need acceptance', async () => { + it('runs real-inference ReadNeedComprehension through one ThricifiedGeneration contract when enabled', async () => { + process.env.BITCODE_ASSET_PACK_REAL_INFERENCE = '1'; + const llm = jest + .fn() + .mockResolvedValueOnce({ + content: JSON.stringify({ + analysis: 'The reader needs a source-bound Terminal readiness AssetPack.', + steps: ['read request', 'bound source revision', 'extract closure criteria'], + conclusion: 'Synthesize a narrow Need for Terminal readiness evidence.', + confidence: 0.91, + }), + }) + .mockResolvedValueOnce({ + content: JSON.stringify({ + quality: 0.88, + issues: [], + suggestions: ['Keep protected source withheld before settlement.'], + approved: true, + }), + }) + .mockResolvedValueOnce({ + content: JSON.stringify({ + requirements: [ + 'Find source-bound evidence for the Terminal Deposit and Reading path.', + 'Keep the Need constrained to the selected repository revision.', + ], + closureCriteria: [ + 'Finding Fits discovery must return source-bound fit deposit ids.', + 'AssetPack preview must expose only measurements and non-source metadata before settlement.', + ], + failureModes: ['protected_source_disclosed_before_settlement'], + targetArtifactKinds: ['asset-pack-evidence', 'proof-root'], + proofExpectations: ['ThricifiedGeneration telemetry contains reasoning, judgment, and typed output.'], + }), + }); + const execution = fakeInferenceExecution(llm); + + const need = await synthesizeReadNeedForPipelineInputWithInference(input, execution); + + expect(llm).toHaveBeenCalledTimes(3); + expect(need.requirements).toContain('Find source-bound evidence for the Terminal Deposit and Reading path.'); + expect(need.closureCriteria).toContain('Finding Fits discovery must return source-bound fit deposit ids.'); + expect(need.failureModes).toContain('protected_source_disclosed_before_settlement'); + expect(need.targetArtifactKinds).toEqual(['asset-pack-evidence', 'proof-root']); + expect(need.proofExpectations).toContain( + 'ThricifiedGeneration telemetry contains reasoning, judgment, and typed output.' + ); + expect(need.reviewState).toBe('needs_acceptance'); + expect(execution.store).toHaveBeenCalledWith('bounded-inference', 'mode', 'thricified-generation'); + expect(execution.store).toHaveBeenCalledWith('bounded-inference', 'status', 'success'); + expect(execution.store).toHaveBeenCalledWith( + 'llm', + 'input', + expect.objectContaining({ + generationSequence: ['reason', 'judge', 'structured_output'], + }) + ); + }); + + it('blocks strict Finding Fits search before Need acceptance', async () => { const result = await runDepositorySearchForPipelineInput({ ...input, requireAcceptedReadNeed: true, @@ -101,14 +171,14 @@ describe('Read-Need synthesis and Need-Fit admission', () => { expect(result.resultState).toBe('blocked_readiness'); expect(result.resultReasons).toEqual( expect.arrayContaining([ - 'Need-Fit search requires an accepted Read-Need before depository candidate recall.', + 'Finding Fits search requires an accepted Read-Need before depository discovery.', 'accepted_read_need_missing', ]) ); expect(result.candidateRanking).toHaveLength(0); }); - it('uses the accepted Need measurement and source constraints for Fit search', async () => { + it('uses the accepted Need measurement and source constraints for Finding Fits', async () => { const acceptedReadNeed = acceptReadNeed( synthesizeReadNeedForPipelineInput(input), '2026-05-18T00:00:00.000Z' @@ -224,3 +294,21 @@ describe('Read-Need synthesis and Need-Fit admission', () => { expect(resolveAssetPackReadRightState({ policyDenied: true })).toBe('denied'); }); }); + +function fakeInferenceExecution(llm: jest.Mock) { + const values = new Map(); + let execution: any; + execution = { + child: jest.fn(() => execution), + getRoot: jest.fn(() => execution), + store: jest.fn((namespace: string, key: string, value: unknown) => { + values.set(`${namespace}:${key}`, value); + }), + get: jest.fn((namespace: string, key: string) => values.get(`${namespace}:${key}`)), + findUp: jest.fn((namespace: string, key: string) => values.get(`${namespace}:${key}`)), + llms: { + getDefaultLLM: jest.fn(() => llm), + }, + }; + return execution; +} diff --git a/packages/pipelines/asset-pack/src/__tests__/reading-pipeline-contract.test.ts b/packages/pipelines/asset-pack/src/__tests__/reading-pipeline-contract.test.ts new file mode 100644 index 000000000..e24f29b75 --- /dev/null +++ b/packages/pipelines/asset-pack/src/__tests__/reading-pipeline-contract.test.ts @@ -0,0 +1,141 @@ +import { + READ_FINDING_FITS_SYNTHESIS, + READ_FINDING_FITS_SYNTHESIS_CONTRACT, + READ_NEED_COMPREHENSION_SYNTHESIS, + READ_NEED_COMPREHENSION_SYNTHESIS_CONTRACT, + READING_PIPELINE_CONTRACTS, + listReadingPipelineContractSummaries, +} from '../reading-pipeline-contract'; + +describe('Reading pipeline contracts', () => { + it('names the two V28 Reading pipelines and prefixes every contract id under the pipeline name', () => { + expect(READING_PIPELINE_CONTRACTS.map((contract) => contract.pipelineName)).toEqual([ + READ_NEED_COMPREHENSION_SYNTHESIS, + READ_FINDING_FITS_SYNTHESIS, + ]); + + for (const contract of READING_PIPELINE_CONTRACTS) { + for (const phase of contract.phases) { + expect(phase.phaseId.startsWith(`${contract.pipelineName}.`)).toBe(true); + for (const agent of phase.agents) { + expect(agent.kind).toBe('ptrr-agent'); + expect(agent.agentId.startsWith(`${contract.pipelineName}.`)).toBe(true); + expect(agent.objectiveId.startsWith(`${contract.pipelineName}.`)).toBe(true); + expect(agent.promptRegistry.factory).toBe('factoryAgentWithPTRR'); + expect(agent.promptRegistry.carrier).toBe('prompt+stepPrompts'); + expect(agent.promptRegistry.agentPromptId.startsWith(`${contract.pipelineName}.prompt.`)).toBe(true); + expect(agent.promptRegistry.promptPartNamespaces).toEqual({ + agent: 'agent/*', + ptrrStep: 'ptrr/*/purpose', + generation: 'generation:*', + failsafe: 'failsafe:*', + }); + expect(agent.ptrrSteps.map((step) => step.ptrrStepName)).toEqual(['plan', 'try', 'refine', 'retry']); + for (const promptId of Object.values(agent.promptRegistry.ptrrStepPromptIds)) { + expect(promptId.startsWith(`${contract.pipelineName}.prompt.`)).toBe(true); + } + for (const ptrrStep of agent.ptrrSteps) { + expect(ptrrStep.ptrrStepId.startsWith(`${contract.pipelineName}.`)).toBe(true); + expect(ptrrStep.thricifiedGenerations).toHaveLength(3); + for (const thricifiedGenerationId of ptrrStep.thricifiedGenerationIds) { + expect(thricifiedGenerationId.startsWith(`${contract.pipelineName}.thricified-generation.`)).toBe(true); + } + for (const thricifiedGeneration of ptrrStep.thricifiedGenerations) { + expect(thricifiedGeneration.thricifiedGenerationId.startsWith(`${contract.pipelineName}.thricified-generation.`)).toBe(true); + expect(thricifiedGeneration.reasonPromptId.startsWith(`${contract.pipelineName}.prompt.`)).toBe(true); + expect(thricifiedGeneration.judgePromptId.startsWith(`${contract.pipelineName}.prompt.`)).toBe(true); + expect(thricifiedGeneration.structuredOutputPromptId.startsWith(`${contract.pipelineName}.prompt.`)).toBe(true); + expect(thricifiedGeneration.returnTypes).toEqual({ + reason: 'Reasoning', + judge: 'Judgment', + structuredOutput: ptrrStep.outputType, + }); + } + for (const telemetry of ptrrStep.telemetry) { + expect(telemetry.startsWith(`${contract.pipelineName}.telemetry.`)).toBe(true); + } + for (const tool of ptrrStep.tools) { + expect(tool.toolId.startsWith(`${contract.pipelineName}.tool.`)).toBe(true); + } + } + } + } + } + }); + + it('locks ReadNeedComprehensionSynthesis to request, comprehend, measure, and review contracts', () => { + expect(READ_NEED_COMPREHENSION_SYNTHESIS_CONTRACT.uxStepIds).toEqual([ + 'request-read', + 'review-synthesized-need', + ]); + expect(READ_NEED_COMPREHENSION_SYNTHESIS_CONTRACT.phases.map((phase) => phase.phaseId)).toEqual([ + 'ReadNeedComprehensionSynthesis.request', + 'ReadNeedComprehensionSynthesis.comprehend', + 'ReadNeedComprehensionSynthesis.measure', + 'ReadNeedComprehensionSynthesis.review', + ]); + const modelSteps = READ_NEED_COMPREHENSION_SYNTHESIS_CONTRACT.phases + .flatMap((phase) => phase.agents) + .flatMap((agent) => agent.ptrrSteps) + .filter((step) => step.kind === 'model-structured'); + expect(modelSteps).toHaveLength(4); + for (const modelStep of modelSteps) { + expect(modelStep.prompt?.templateId).toBe('ReadNeedComprehensionSynthesis.prompt.need-synthesis'); + expect(modelStep.outputType).toBe('ReadNeed'); + } + }); + + it('locks ReadFindingFitsSynthesis to accepted-Need admission, depository search, synthesis, preview, and settlement contracts', () => { + expect(READ_FINDING_FITS_SYNTHESIS_CONTRACT.uxStepIds).toEqual([ + 'request-fit', + 'review-synthesized-asset-pack', + 'buy-asset-pack-settle', + ]); + expect(READ_FINDING_FITS_SYNTHESIS_CONTRACT.phases.map((phase) => phase.phaseId)).toEqual([ + 'ReadFindingFitsSynthesis.admit', + 'ReadFindingFitsSynthesis.prepare', + 'ReadFindingFitsSynthesis.discovery', + 'ReadFindingFitsSynthesis.implementation', + 'ReadFindingFitsSynthesis.validate', + 'ReadFindingFitsSynthesis.preview', + 'ReadFindingFitsSynthesis.settle', + ]); + const allSteps = READ_FINDING_FITS_SYNTHESIS_CONTRACT.phases + .flatMap((phase) => phase.agents) + .flatMap((agent) => agent.ptrrSteps); + expect(allSteps.filter((step) => step.kind === 'model-structured')).toHaveLength(16); + expect(allSteps.flatMap((step) => step.tools).map((tool) => tool.toolId)).toEqual( + expect.arrayContaining([ + 'ReadFindingFitsSynthesis.tool.lexical-depository-search', + 'ReadFindingFitsSynthesis.tool.vector-depository-search', + 'ReadFindingFitsSynthesis.tool.verification-evidence', + 'ReadFindingFitsSynthesis.tool.vcs-create-pull-request', + ]), + ); + }); + + it('summarizes counts used by V28 promotion proof and Terminal telemetry QA', () => { + expect(listReadingPipelineContractSummaries()).toEqual([ + expect.objectContaining({ + pipelineName: READ_NEED_COMPREHENSION_SYNTHESIS, + phaseCount: 4, + agentCount: 4, + ptrrAgentCount: 4, + ptrrStepCount: 16, + modelStructuredPtrrStepCount: 4, + thricifiedGenerationCount: 48, + toolCount: 0, + }), + expect.objectContaining({ + pipelineName: READ_FINDING_FITS_SYNTHESIS, + phaseCount: 7, + agentCount: 8, + ptrrAgentCount: 8, + ptrrStepCount: 32, + modelStructuredPtrrStepCount: 16, + thricifiedGenerationCount: 96, + toolCount: 4, + }), + ]); + }); +}); diff --git a/packages/pipelines/asset-pack/src/__tests__/setup-agents.test.ts b/packages/pipelines/asset-pack/src/__tests__/setup-agents.test.ts index d287ffb60..2215309d7 100644 --- a/packages/pipelines/asset-pack/src/__tests__/setup-agents.test.ts +++ b/packages/pipelines/asset-pack/src/__tests__/setup-agents.test.ts @@ -55,7 +55,7 @@ describe('AssetPack setup agents', () => { ); }); - it('builds a deterministic setup plan from read and fit search state', async () => { + it('builds a deterministic setup plan from read and Finding Fits state', async () => { const execution = executionStub(); const result = await setupPlanAgent( { diff --git a/packages/pipelines/asset-pack/src/agents/discovery-agents.ts b/packages/pipelines/asset-pack/src/agents/discovery-agents.ts index 031d74a96..b87b061b3 100644 --- a/packages/pipelines/asset-pack/src/agents/discovery-agents.ts +++ b/packages/pipelines/asset-pack/src/agents/discovery-agents.ts @@ -130,7 +130,7 @@ export async function AssetPackDiscoveryPhaseUnderstandRequirementsAgent( ], scope: { included: [ - 'Deposit candidate recall', + 'Finding Fits deposit discovery', 'Read/Fit quality review', 'Source-bound AssetPack synthesis', 'Validation and finish evidence', @@ -297,9 +297,9 @@ export async function AssetPackDiscoveryPhaseResearchApproachAgent( methodology: 'Source-bound depository fit synthesis with explicit proof, telemetry, and ledger readback checkpoints.', phases: [ { - name: 'Candidate fit review', - description: 'Use the preprocessed depository search result and selected candidate ids as the admissible candidate set.', - assetPackSynthesisArtifacts: ['fit-receipt', 'candidate-ranking-root'] + name: 'Fit deposit review', + description: 'Use the preprocessed depository search result and fit deposit ids as the admissible knowledge set.', + assetPackSynthesisArtifacts: ['fit-receipt', 'fit-deposit-ranking-root'] }, { name: 'AssetPack synthesis', @@ -449,8 +449,8 @@ export async function AssetPackDiscoveryPhasePlanImplementationAgent( milestones: [ { name: 'Preserve fit evidence', - description: 'Carry resultState, candidate asset ids, query root, ranking root, and embedding policy into synthesis artifacts.', - completionCriteria: ['fitResult.resultState is retained', 'selected candidate ids are retained'] + description: 'Carry resultState, fit deposit asset ids, query root, ranking root, and embedding policy into synthesis artifacts.', + completionCriteria: ['fitResult.resultState is retained', 'fit deposit ids are retained'] }, { name: 'Synthesize AssetPack', diff --git a/packages/pipelines/asset-pack/src/agents/implementation/asset-pack-synthesize-artifacts-agent.ts b/packages/pipelines/asset-pack/src/agents/implementation/asset-pack-synthesize-artifacts-agent.ts index 59adc32ae..cb3c7845f 100644 --- a/packages/pipelines/asset-pack/src/agents/implementation/asset-pack-synthesize-artifacts-agent.ts +++ b/packages/pipelines/asset-pack/src/agents/implementation/asset-pack-synthesize-artifacts-agent.ts @@ -167,6 +167,23 @@ async function runBoundedAssetPackSynthesis( execution, schema: AssetPackSynthesisOutputSchema, fallback: () => baseline, + promptTemplate: { + templateId: 'ReadFindingFitsSynthesis.prompt.asset-pack-synthesis', + system: [ + 'You are the Bitcode AssetPack synthesis agent.', + 'Synthesize one Read-satisfaction AssetPack from source-bound depository fit evidence.', + 'Do not invent files, external proof, pull requests, BTC broadcasts, or ledger settlement.', + 'Return only typed JSON that can be validated and finished by the pipeline.', + ].join('\n'), + user: JSON.stringify({ + requestedShape: '{{requestedShape}}', + read: '{{read}}', + deliveryMechanismTemplate: '{{deliveryMechanismTemplate}}', + sourceRevision: '{{sourceRevision}}', + fitResult: '{{fitResult}}', + baselineProofEvidence: '{{baselineProofEvidence}}', + }, null, 2), + }, systemPrompt: [ 'You are the Bitcode AssetPack synthesis agent.', 'Synthesize one Read-satisfaction AssetPack from source-bound depository fit evidence.', @@ -225,9 +242,11 @@ function buildDeterministicAssetPackSynthesis( findExecutionValue(execution, 'fit', 'result') ?? findExecutionValue(execution, 'depository/search', 'result') ?? {}; - const selectedCandidateAssetIds = Array.isArray(fitResult?.selectedCandidateAssetIds) - ? fitResult.selectedCandidateAssetIds - : []; + const fitDepositAssetIds = Array.isArray(fitResult?.fitDepositAssetIds) + ? fitResult.fitDepositAssetIds + : Array.isArray(fitResult?.selectedCandidateAssetIds) + ? fitResult.selectedCandidateAssetIds + : []; const repository = input?.repository ?? input?.sourceRevision ?? @@ -244,7 +263,7 @@ function buildDeterministicAssetPackSynthesis( `Read: ${read}`, `Repository revision: ${repositoryLabel}`, `Fit result: ${fitResult?.resultState ?? 'not-yet-classified'}`, - `Selected candidate assets: ${selectedCandidateAssetIds.join(', ') || 'none'}`, + `Fit deposit assets: ${fitDepositAssetIds.join(', ') || 'none'}`, `Query root: ${fitResult?.queryRoot ?? 'not recorded'}`, `Ranking root: ${fitResult?.rankingRoot ?? 'not recorded'}`, `Embedding policy: ${fitResult?.embeddingPolicy?.provider ?? 'unknown'} ${fitResult?.embeddingPolicy?.model ?? ''}`.trim() @@ -252,7 +271,7 @@ function buildDeterministicAssetPackSynthesis( const summary = [ 'Source-bound Read-satisfaction AssetPack synthesized by the staging pipeline.', `The pack binds the admitted Read to ${repositoryLabel}.`, - `Fit posture: ${fitResult?.resultState ?? 'not-yet-classified'} with ${selectedCandidateAssetIds.length} selected candidate(s).`, + `Finding Fits posture: ${fitResult?.resultState ?? 'not-yet-classified'} with ${fitDepositAssetIds.length} qualifying fit deposit(s).`, 'Validation must preserve proof, telemetry, delivery, and ledger readback before settlement trust.' ].join(' '); return { @@ -278,7 +297,7 @@ function buildDeterministicAssetPackSynthesis( summary, proofEvidence, reviewNotes: [ - 'Read/Fit candidate evidence carried forward for validation and finish.' + 'Finding Fits deposit evidence carried forward for validation and finish.' ] }, assetPack: { @@ -385,6 +404,7 @@ function compactFitResult(fitResult: any) { return { resultState: fitResult.resultState, resultReasons: fitResult.resultReasons, + fitDepositAssetIds: fitResult.fitDepositAssetIds || fitResult.selectedCandidateAssetIds, selectedCandidateAssetIds: fitResult.selectedCandidateAssetIds, searchedAssetCount: fitResult.searchedAssetCount, queryRoot: fitResult.queryRoot, diff --git a/packages/pipelines/asset-pack/src/agents/setup/asset-pack-comprehend-read-agent.ts b/packages/pipelines/asset-pack/src/agents/setup/asset-pack-comprehend-read-agent.ts index d7279cb70..38eb1eede 100644 --- a/packages/pipelines/asset-pack/src/agents/setup/asset-pack-comprehend-read-agent.ts +++ b/packages/pipelines/asset-pack/src/agents/setup/asset-pack-comprehend-read-agent.ts @@ -109,6 +109,26 @@ async function runBoundedReadComprehension(input: any, execution: any) { execution, schema: BoundedReadComprehensionSchema, fallback: () => baseline, + promptTemplate: { + templateId: 'ReadFindingFitsSynthesis.prompt.read-comprehension', + system: [ + 'You are a Bitcode setup Read-comprehension agent.', + 'Translate the expressed Read into one auditable Read model for AssetPack synthesis.', + 'Return source-bound evidence only. Do not claim settlement or finality beyond provided proof roots/readbacks.', + 'Respond only with JSON matching the requested shape.', + ].join('\n'), + user: JSON.stringify({ + requestedShape: '{{requestedShape}}', + read: '{{read}}', + definitionOfRead: '{{definitionOfRead}}', + repository: '{{repository}}', + sourceRevision: '{{sourceRevision}}', + deposit: '{{deposit}}', + fitResult: '{{fitResult}}', + baselineReadModel: '{{baselineReadModel}}', + baselineSatisfactionCriteria: '{{baselineSatisfactionCriteria}}', + }, null, 2), + }, systemPrompt: [ 'You are a Bitcode setup Read-comprehension agent.', 'Translate the expressed Read into one auditable Read model for AssetPack synthesis.', diff --git a/packages/pipelines/asset-pack/src/agents/setup/asset-pack-setup-plan-agent.ts b/packages/pipelines/asset-pack/src/agents/setup/asset-pack-setup-plan-agent.ts index 66dde8f21..8b95c2de9 100644 --- a/packages/pipelines/asset-pack/src/agents/setup/asset-pack-setup-plan-agent.ts +++ b/packages/pipelines/asset-pack/src/agents/setup/asset-pack-setup-plan-agent.ts @@ -131,6 +131,21 @@ export default async function setupPlanAgent(input: any, execution: any) { execution, schema: PlanSchema, fallback: () => baseline, + promptTemplate: { + templateId: 'ReadFindingFitsSynthesis.prompt.setup-plan', + system: [ + 'You are the Bitcode AssetPack setup-plan agent.', + 'Produce one concise source-bound plan for a Read/Fit pipeline run.', + 'Do not claim settlement, delivery, or finality before later phases validate and finish.', + 'Respond only with JSON shaped as { "plan": string }.', + ].join('\n'), + user: JSON.stringify({ + read: '{{read}}', + repository: '{{repository}}', + fitResult: '{{fitResult}}', + baselinePlan: '{{baselinePlan}}', + }, null, 2), + }, systemPrompt: [ 'You are the Bitcode AssetPack setup-plan agent.', 'Produce one concise source-bound plan for a Read/Fit pipeline run.', diff --git a/packages/pipelines/asset-pack/src/bounded-structured-inference.ts b/packages/pipelines/asset-pack/src/bounded-structured-inference.ts index f38760ea0..3879de187 100644 --- a/packages/pipelines/asset-pack/src/bounded-structured-inference.ts +++ b/packages/pipelines/asset-pack/src/bounded-structured-inference.ts @@ -10,6 +10,11 @@ type BoundedInferenceParams = { step?: string; systemPrompt: string; userPrompt: string; + promptTemplate?: { + system?: string; + user?: string; + templateId?: string; + }; schema: z.ZodType; fallback: () => T; execution: any; @@ -22,12 +27,43 @@ type ResolvedLLM = { source: string; }; +const stringListSchema = z.preprocess( + (value) => { + if (typeof value === 'string') return [value]; + return value; + }, + z.array(z.string()).default([]), +); + +const ReasoningSchema = z.object({ + analysis: z.string().default(''), + steps: stringListSchema, + conclusion: z.string().default(''), + confidence: z.coerce.number().min(0).max(1).catch(0), + useTools: z.array(z.any()).optional(), +}); + +const JudgmentSchema = z.object({ + quality: z.coerce.number().min(0).max(1).catch(0), + issues: stringListSchema, + suggestions: stringListSchema, + approved: z.preprocess((value) => { + if (typeof value === 'string') { + const normalized = value.trim().toLowerCase(); + if (normalized === 'true') return true; + if (normalized === 'false') return false; + } + return value; + }, z.boolean().default(false)), +}); + export async function runBoundedStructuredInference({ agentName, phase, step = 'bounded', systemPrompt, userPrompt, + promptTemplate, schema, fallback, execution, @@ -42,13 +78,22 @@ export async function runBoundedStructuredInference({ agentExecution?.store?.('phase', 'current', phase); agentExecution?.store?.('agent', 'name', agentName); agentExecution?.store?.('step', 'name', step); - agentExecution?.store?.('bounded-inference', 'mode', 'single-structured-generation'); + agentExecution?.store?.('bounded-inference', 'mode', 'thricified-generation'); agentExecution?.store?.('llm', 'input', { messages, + promptTemplate: promptTemplate || { + system: systemPrompt, + user: userPrompt, + }, + interpolatedPrompt: { + system: systemPrompt, + user: userPrompt, + }, phase, agent: agentName, step, - generation: 'structured_output', + generation: 'thricified-generation', + generationSequence: ['reason', 'judge', 'structured_output'], }); } catch {} @@ -70,6 +115,7 @@ export async function runBoundedStructuredInference({ agentExecution?.store?.('bounded-inference', 'status', 'fallback-no-llm'); agentExecution?.store?.('llm', 'parsedOutput', { parsed: fallbackResult, + parsedTypedOutput: fallbackResult, phase, agent: agentName, step, @@ -88,8 +134,58 @@ export async function runBoundedStructuredInference({ }); } catch {} + const reasoningOutput = await resolvedLLM.llm({ + messages: [ + ...messages, + { + role: 'user' as const, + content: [ + 'ThricifiedGeneration stage 1/3: reason.', + 'Return only JSON with keys: analysis, steps, conclusion, confidence.', + ].join('\n'), + }, + ], + config: { + responseFormat: 'json', + temperature: 0.2, + maxTokens: 2048, + }, + }); + const reasoning = ReasoningSchema.parse(JSON.parse(extractJsonFromResponse(reasoningOutput.content))); + + const judgmentOutput = await resolvedLLM.llm({ + messages: [ + ...messages, + { + role: 'user' as const, + content: [ + 'ThricifiedGeneration stage 2/3: judge the prior reasoning.', + 'Return only JSON with keys: quality, issues, suggestions, approved.', + `Reasoning JSON: ${JSON.stringify(reasoning)}`, + ].join('\n'), + }, + ], + config: { + responseFormat: 'json', + temperature: 0.2, + maxTokens: 2048, + }, + }); + const judgment = JudgmentSchema.parse(JSON.parse(extractJsonFromResponse(judgmentOutput.content))); + const output = await resolvedLLM.llm({ - messages, + messages: [ + ...messages, + { + role: 'user' as const, + content: [ + 'ThricifiedGeneration stage 3/3: structured output.', + 'Use the original task, the reasoning, and the judgment to return only the requested typed JSON.', + `Reasoning JSON: ${JSON.stringify(reasoning)}`, + `Judgment JSON: ${JSON.stringify(judgment)}`, + ].join('\n'), + }, + ], config: { responseFormat: 'json', temperature: 0.2, @@ -97,16 +193,41 @@ export async function runBoundedStructuredInference({ }, }); try { + agentExecution?.store?.('llm', 'reasoningOutput', { + content: reasoningOutput.content, + rawResponse: reasoningOutput.content, + parsedTypedOutput: reasoning, + phase, + agent: agentName, + step, + generation: 'reason', + provider: reasoningOutput.metadata?.provider ?? resolvedLLM.provider, + model: reasoningOutput.metadata?.model ?? resolvedLLM.model, + }); + agentExecution?.store?.('llm', 'judgmentOutput', { + content: judgmentOutput.content, + rawResponse: judgmentOutput.content, + parsedTypedOutput: judgment, + phase, + agent: agentName, + step, + generation: 'judge', + provider: judgmentOutput.metadata?.provider ?? resolvedLLM.provider, + model: judgmentOutput.metadata?.model ?? resolvedLLM.model, + }); agentExecution?.store?.('llm', 'output', { content: output.content, + rawResponse: output.content, phase, agent: agentName, step, generation: 'structured_output', + reasoning, + judgment, provider: output.metadata?.provider ?? resolvedLLM.provider, model: output.metadata?.model ?? resolvedLLM.model, }); - agentExecution?.store?.('llm', 'usage', output.usage); + agentExecution?.store?.('llm', 'usage', mergeUsage(reasoningOutput.usage, judgmentOutput.usage, output.usage)); } catch {} const parsed = realInferenceRequired @@ -115,10 +236,13 @@ export async function runBoundedStructuredInference({ try { agentExecution?.store?.('llm', 'parsedOutput', { parsed, + parsedTypedOutput: parsed, phase, agent: agentName, step, generation: 'structured_output', + reasoning, + judgment, provider: output.metadata?.provider ?? resolvedLLM.provider, model: output.metadata?.model ?? resolvedLLM.model, }); @@ -140,6 +264,7 @@ export async function runBoundedStructuredInference({ agentExecution?.store?.('bounded-inference', 'error', error instanceof Error ? error.message : String(error)); agentExecution?.store?.('llm', 'parsedOutput', { parsed: fallbackResult, + parsedTypedOutput: fallbackResult, phase, agent: agentName, step, @@ -223,3 +348,16 @@ function hasProviderCredential(provider: string): boolean { return true; } } + +function mergeUsage(...usages: Array | undefined>): Record | undefined { + const merged: Record = {}; + for (const usage of usages) { + if (!usage) continue; + for (const [key, value] of Object.entries(usage)) { + if (typeof value === 'number' && Number.isFinite(value)) { + merged[key] = (merged[key] || 0) + value; + } + } + } + return Object.keys(merged).length ? merged : undefined; +} diff --git a/packages/pipelines/asset-pack/src/depository-search.ts b/packages/pipelines/asset-pack/src/depository-search.ts index 5fad0c970..9ed223c9e 100644 --- a/packages/pipelines/asset-pack/src/depository-search.ts +++ b/packages/pipelines/asset-pack/src/depository-search.ts @@ -1,7 +1,7 @@ import { createHash } from 'node:crypto'; import { buildAssetPackEmbeddingPolicy } from './embedding-config'; import { - admitNeedFitSearch, + admitReadFindingFits, isAcceptedReadNeed, readNeedToDepositorySearchRead, resolveReadNeedFromPipelineInput, @@ -220,6 +220,8 @@ export interface DepositorySearchResult { read: DepositorySearchRead; thresholds: DepositorySearchThresholds; searchedAssetCount: number; + fitDepositAssetIds: string[]; + fitDeposits: DepositoryCandidate[]; selectedCandidateAssetIds: string[]; selectedCandidates: DepositoryCandidate[]; rejectedCandidates: DepositoryCandidate[]; @@ -231,10 +233,13 @@ export interface DepositorySearchResult { createdAt: string; } +export type DepositoryFitsResult = DepositorySearchResult; + export interface DepositoryFitResultEvidence { schema: 'bitcode.asset-pack.fit-result'; resultState: AssetPackFitResultState; resultReasons: string[]; + fitDepositAssetIds: string[]; selectedCandidateAssetIds: string[]; queryRoot: string; rankingRoot: string; @@ -242,6 +247,7 @@ export interface DepositoryFitResultEvidence { embeddingPolicy: ReturnType; selectionTrace: { selectedCandidates: DepositoryCandidateFitEvidence[]; + fitDeposits: DepositoryCandidateFitEvidence[]; blockedCandidates: DepositoryCandidateFitEvidence[]; candidateRanking: DepositoryCandidateFitEvidence[]; rejectedCandidateCount: number; @@ -846,6 +852,7 @@ export function buildDepositoryFitResultEvidence( schema: 'bitcode.asset-pack.fit-result', resultState: result.resultState, resultReasons: result.resultReasons, + fitDepositAssetIds: result.fitDepositAssetIds, selectedCandidateAssetIds: result.selectedCandidateAssetIds, queryRoot: result.queryRoot, rankingRoot: result.rankingRoot, @@ -853,6 +860,7 @@ export function buildDepositoryFitResultEvidence( embeddingPolicy: result.embeddingPolicy, selectionTrace: { selectedCandidates: result.selectedCandidates.map(summarizeDepositoryCandidateForFitEvidence), + fitDeposits: result.fitDeposits.map(summarizeDepositoryCandidateForFitEvidence), blockedCandidates: result.blockedCandidates.map(summarizeDepositoryCandidateForFitEvidence), candidateRanking: result.candidateRanking.map(summarizeDepositoryCandidateForFitEvidence), rejectedCandidateCount: result.rejectedCandidates.length, @@ -905,7 +913,7 @@ function resultStateFor(input: { return { resultState: 'worthy_fit', resultReasons: [ - `Selected ${worthy.length} proof-bearing AssetPack candidate${worthy.length === 1 ? '' : 's'} for this Read.`, + `Selected ${worthy.length} proof-bearing fit deposit${worthy.length === 1 ? '' : 's'} for this Read.`, ], }; } @@ -996,6 +1004,8 @@ export async function searchDepositoryAssetSpace( read: input.read, thresholds, searchedAssetCount: assets.length, + fitDepositAssetIds: selected.map((candidate) => candidate.assetId), + fitDeposits: selected, selectedCandidateAssetIds: selected.map((candidate) => candidate.assetId), selectedCandidates: selected, rejectedCandidates: rejected, @@ -1286,6 +1296,8 @@ function storeDepositorySearchToolResult( resultState: result.resultState, resultReasons: result.resultReasons, searchedAssetCount: result.searchedAssetCount, + fitDepositAssetIds: result.fitDepositAssetIds, + fitDepositCount: result.fitDeposits.length, selectedCandidateAssetIds: result.selectedCandidateAssetIds, selectedCandidateCount: result.selectedCandidates.length, blockedCandidateCount: result.blockedCandidates.length, @@ -1301,7 +1313,7 @@ function storeDepositorySearchToolResult( }); } -function buildBlockedNeedFitSearchResult(input: { +function buildBlockedReadFindingFitsResult(input: { read: DepositorySearchRead; assets: DepositoryAsset[]; blockers: string[]; @@ -1325,12 +1337,14 @@ function buildBlockedNeedFitSearchResult(input: { schema: 'bitcode.asset-pack.depository-search', resultState: 'blocked_readiness', resultReasons: [ - 'Need-Fit search requires an accepted Read-Need before depository candidate recall.', + 'Finding Fits search requires an accepted Read-Need before depository discovery.', ...input.blockers, ], read: input.read, thresholds, searchedAssetCount: input.assets.length, + fitDepositAssetIds: [], + fitDeposits: [], selectedCandidateAssetIds: [], selectedCandidates: [], rejectedCandidates: [], @@ -1347,7 +1361,7 @@ export async function runDepositorySearchForPipelineInput( input: unknown, execution?: { store?: (namespace: string, key: string, value: unknown) => void; parent?: unknown } ): Promise { - const admission = admitNeedFitSearch(input); + const admission = admitReadFindingFits(input); const read = normalizeDepositorySearchRead(input); const assets = normalizePipelineDepositoryAssets(input); const providers = [createLexicalDepositorySearchProvider()]; @@ -1357,7 +1371,7 @@ export async function runDepositorySearchForPipelineInput( assets, providers, }) - : buildBlockedNeedFitSearchResult({ + : buildBlockedReadFindingFitsResult({ read, assets, blockers: admission.blockers, @@ -1371,7 +1385,7 @@ export async function runDepositorySearchForPipelineInput( target.store('read/need', 'measurementRoot', admission.acceptedNeed.measurementRoot); target.store('read/need', 'needId', admission.acceptedNeed.needId); } - target.store('read/need-fit', 'admission', admission); + target.store('read/finding-fits', 'admission', admission); target.store('depository/search', 'result', result); target.store('depository/search', 'candidateRanking', result.candidateRanking); target.store('depository/search', 'selectedCandidates', result.selectedCandidates); diff --git a/packages/pipelines/asset-pack/src/index.ts b/packages/pipelines/asset-pack/src/index.ts index c8cbe14ae..092078708 100644 --- a/packages/pipelines/asset-pack/src/index.ts +++ b/packages/pipelines/asset-pack/src/index.ts @@ -107,6 +107,8 @@ function factoryPreprocess(): Executor { const depositorySearch = await runDepositorySearchForPipelineInput(processedInput, execution); try { processedInput.depositorySearchResult = depositorySearch; } catch {} try { processedInput.depositCandidates = depositorySearch.selectedCandidates; } catch {} + try { processedInput.fitDeposits = depositorySearch.fitDeposits; } catch {} + try { processedInput.fitDepositAssetIds = depositorySearch.fitDepositAssetIds; } catch {} try { processedInput.fitResult = buildDepositoryFitResultEvidence(depositorySearch); } catch {} @@ -267,6 +269,7 @@ export * from './phases'; export * from './types/AssetPackWrittenAssetType'; export * from './depository-search'; export * from './embedding-config'; +export * from './reading-pipeline-contract'; export { AssetPackCloneVCSRepositoryAgent } from './agents/setup/asset-pack-clone-vcs-repository-agent'; export { AssetPackComprehendReadAgent, @@ -276,7 +279,7 @@ export { export { AssetPackSynthesizeArtifactsAgent } from './agents/implementation/asset-pack-synthesize-artifacts-agent'; export { acceptReadNeed, - admitNeedFitSearch, + admitReadFindingFits, buildAssetPackSourceSafePreview, buildShareToFeePreview, isAcceptedReadNeed, @@ -288,7 +291,7 @@ export { type AssetPackReadRightState, type AssetPackSourceSafePreview, type ReadNeed, - type ReadNeedFitAdmission, + type ReadFindingFitsAdmission, type ReadNeedMeasurementDimension, type ReadNeedReviewState, type ShareToFeeQuote, diff --git a/packages/pipelines/asset-pack/src/phases/finish.ts b/packages/pipelines/asset-pack/src/phases/finish.ts index 8b2443a4c..693885db8 100644 --- a/packages/pipelines/asset-pack/src/phases/finish.ts +++ b/packages/pipelines/asset-pack/src/phases/finish.ts @@ -5,7 +5,7 @@ * 1. Provide AssetPacks/AssetPackPartials to third-party destinations * 2. Gather completion metrics from all phases * 3. Generate AssetPack completion evidence - * 4. Finalize pipeline execution + * 4. Finalize the pipeline run */ import { createPhaseRunner, PhaseConfig } from '@bitcode/pipelines-generics'; diff --git a/packages/pipelines/asset-pack/src/preprocess.ts b/packages/pipelines/asset-pack/src/preprocess.ts index a08fc1c8d..46977f8d3 100644 --- a/packages/pipelines/asset-pack/src/preprocess.ts +++ b/packages/pipelines/asset-pack/src/preprocess.ts @@ -53,7 +53,7 @@ export async function initializeAssetPackPipeline(execution: PipelineExecution) if (typeof (llmRegistry as any).setDefaultProvider === 'function') { (llmRegistry as any).setDefaultProvider(provider); } - // Global default for all pipeline executions + // Global default for all pipeline runs. llmRegistry.configure('*', { model }); execution.llms.setLLMRegistry(llmRegistry as any); } catch (_) { diff --git a/packages/pipelines/asset-pack/src/read-need.ts b/packages/pipelines/asset-pack/src/read-need.ts index 3283975a8..d6cca5e93 100644 --- a/packages/pipelines/asset-pack/src/read-need.ts +++ b/packages/pipelines/asset-pack/src/read-need.ts @@ -1,8 +1,11 @@ import { createHash } from 'node:crypto'; +import { z } from 'zod'; import type { DepositoryFitResultEvidence, DepositorySearchRead, } from './depository-search'; +import { runBoundedStructuredInference } from './bounded-structured-inference'; +import { isAssetPackRealInferenceEnabled } from './runtime-inference-policy'; export type ReadNeedReviewState = | 'needs_acceptance' @@ -48,11 +51,11 @@ export interface ReadNeed { status: 'accepted'; acceptedAt: string; acceptanceRoot: string; - nextStage: 'need_fit_search'; + nextStage: 'finding_fits'; }; } -export interface ReadNeedFitAdmission { +export interface ReadFindingFitsAdmission { admitted: boolean; blockers: string[]; acceptedNeed: ReadNeed | null; @@ -97,6 +100,7 @@ export interface AssetPackSourceSafePreview { resultState: string; resultReasons: string[]; admittedFitQuality: number; + fitDepositAssetIds: string[]; selectedCandidateAssetIds: string[]; queryRoot: string | null; rankingRoot: string | null; @@ -170,6 +174,22 @@ type ReadNeedSourceInput = { feedback?: unknown; }; +const inferredStringListSchema = z.preprocess( + (value) => { + if (typeof value === 'string') return [value]; + return value; + }, + z.array(z.string()).default([]), +); + +export const ReadNeedComprehensionSynthesisSchema = z.object({ + requirements: inferredStringListSchema, + closureCriteria: inferredStringListSchema, + failureModes: inferredStringListSchema, + targetArtifactKinds: inferredStringListSchema, + proofExpectations: inferredStringListSchema, +}); + function sha256(value: string): string { return createHash('sha256').update(value).digest('hex'); } @@ -295,7 +315,7 @@ function defaultRequirements(read: ReturnType): string[] return [ 'Understand the reader request as a bounded source-reading need before searching the depository.', 'Find source-bound deposited evidence that satisfies the accepted Need, not merely related prose.', - 'Preserve proof, measurement, and reconciliation readback requirements through Fit search and AssetPack synthesis.', + 'Preserve proof, measurement, and reconciliation readback requirements through Finding Fits and AssetPack synthesis.', 'Prevent protected source disclosure before preview approval and settlement readback.', ...(read.repositoryFullName ? [`Bind Fit candidates to repository ${read.repositoryFullName}.`] : []), ...(read.sourceCommit ? [`Bind Fit candidates to source commit ${read.sourceCommit}.`] : []), @@ -305,8 +325,8 @@ function defaultRequirements(read: ReturnType): string[] function defaultClosureCriteria(read: ReturnType): string[] { return [ 'The Need has requirements, target artifact kinds, proof expectations, and failure modes.', - 'Candidate recall is source-bound to the accepted Need repository, branch, and commit constraints.', - 'The Fit result exposes query root, ranking root, selected candidate ids, score evidence, and proof/readback posture.', + 'Finding Fits discovery is source-bound to the accepted Need repository, branch, and commit constraints.', + 'The Fit result exposes query root, ranking root, fit deposit ids, score evidence, and proof/readback posture.', 'The AssetPack preview excludes protected source material before settlement.', 'BTC fee, ownership, license, journal, and ledger rows must be read back before unlock.', ...(read.prompt ? [`The Fit must satisfy: ${read.prompt}`] : []), @@ -402,7 +422,7 @@ export function synthesizeReadNeedForPipelineInput(input: ReadNeedSourceInput): protectedSourceDisclosure: 'forbidden_before_settlement', }, proofExpectations: [ - 'source-bound candidate ranking root', + 'source-bound fit deposit ranking root', 'proof root', 'measurement evidence', 'settlement readback before unlock', @@ -416,6 +436,89 @@ export function synthesizeReadNeedForPipelineInput(input: ReadNeedSourceInput): }; } +export async function synthesizeReadNeedForPipelineInputWithInference( + input: ReadNeedSourceInput, + execution?: any, +): Promise { + const fallbackNeed = synthesizeReadNeedForPipelineInput(input); + if (!isAssetPackRealInferenceEnabled()) { + return fallbackNeed; + } + + const read = normalizeSource(input); + const targetArtifactKinds = normalizeTargetArtifactKinds(input); + const explicitClosureCriteria = [ + ...stringArray(input.closureCriteria), + ...stringArray(recordValue(input.read)?.closureCriteria), + ...stringArray(recordValue(input.readRequest)?.closureCriteria), + ...stringArray(recordValue(input.readMeasurement)?.closureCriteria), + ]; + const fallbackStructured = { + requirements: fallbackNeed.requirements, + closureCriteria: fallbackNeed.closureCriteria, + failureModes: fallbackNeed.failureModes, + targetArtifactKinds: fallbackNeed.targetArtifactKinds, + proofExpectations: fallbackNeed.proofExpectations, + }; + const inferred = await runBoundedStructuredInference({ + agentName: 'ReadNeedComprehensionSynthesis.comprehend.need-synthesizer', + phase: 'ReadNeedComprehensionSynthesis.comprehend', + step: 'try', + systemPrompt: [ + 'You are the ReadNeedComprehensionSynthesis PTRR agent for Bitcode Reading.', + 'Synthesize exactly what the reader asked Bitcode to read, no more and no less.', + 'Return only source-constrained Need comprehension fields that can later drive ReadFindingFitsSynthesis.', + 'Do not claim that deposits were searched, that a fit exists, that BTC was paid, or that protected AssetPack source may be shown.', + ].join('\n'), + userPrompt: JSON.stringify({ + read, + targetArtifactKinds, + closureCriteria: explicitClosureCriteria, + failureModes: stringArray(input.failureModes), + feedbackHistory: stringArray(input.feedback), + requiredOutput: + 'requirements, closureCriteria, failureModes, targetArtifactKinds, proofExpectations', + }), + promptTemplate: { + templateId: 'ReadNeedComprehensionSynthesis.prompt.need-synthesis', + system: 'ReadNeedComprehensionSynthesis PTRR agent prompt', + user: 'Read request, source constraints, closure criteria, target artifact kinds, failure modes, and feedback history.', + }, + schema: ReadNeedComprehensionSynthesisSchema, + fallback: () => fallbackStructured, + execution, + }); + + const inferredRequirements = stringArray(inferred.requirements); + const inferredClosureCriteria = stringArray(inferred.closureCriteria); + const inferredFailureModes = stringArray(inferred.failureModes); + const inferredTargetArtifactKinds = stringArray(inferred.targetArtifactKinds); + const inferredProofExpectations = stringArray(inferred.proofExpectations); + const closureCriteria = inferredClosureCriteria.length + ? inferredClosureCriteria + : fallbackNeed.closureCriteria; + const synthesized = synthesizeReadNeedForPipelineInput({ + ...input, + closureCriteria, + failureModes: inferredFailureModes.length + ? inferredFailureModes + : fallbackNeed.failureModes, + targetArtifactKinds: inferredTargetArtifactKinds.length + ? inferredTargetArtifactKinds + : fallbackNeed.targetArtifactKinds, + }); + + return { + ...synthesized, + requirements: inferredRequirements.length + ? inferredRequirements + : synthesized.requirements, + proofExpectations: inferredProofExpectations.length + ? inferredProofExpectations + : synthesized.proofExpectations, + }; +} + export function acceptReadNeed( need: ReadNeed, acceptedAt = new Date().toISOString() @@ -424,7 +527,7 @@ export function acceptReadNeed( needId: need.needId, measurementRoot: need.measurementRoot, acceptedAt, - nextStage: 'need_fit_search', + nextStage: 'finding_fits', }))}`; return { ...need, @@ -433,7 +536,7 @@ export function acceptReadNeed( status: 'accepted', acceptedAt, acceptanceRoot, - nextStage: 'need_fit_search', + nextStage: 'finding_fits', }, }; } @@ -465,7 +568,7 @@ export function shouldRequireAcceptedReadNeed(input: unknown): boolean { ); } -export function admitNeedFitSearch(input: unknown): ReadNeedFitAdmission { +export function admitReadFindingFits(input: unknown): ReadFindingFitsAdmission { const acceptedNeed = resolveReadNeedFromPipelineInput(input); if (isAcceptedReadNeed(acceptedNeed)) { return { admitted: true, blockers: [], acceptedNeed }; @@ -595,18 +698,19 @@ export function buildAssetPackSourceSafePreview(input: { need: input.need, admittedFitQuality, }); - const selectedCandidateAssetIds = fitResult?.selectedCandidateAssetIds || []; + const fitDepositAssetIds = fitResult?.fitDepositAssetIds || fitResult?.selectedCandidateAssetIds || []; + const selectedCandidateAssetIds = fitResult?.selectedCandidateAssetIds || fitDepositAssetIds; const assetPackId = firstString(input.assetPackId) || `asset-pack-${sha256(stableStringify({ needId: input.need.needId, - selectedCandidateAssetIds, + fitDepositAssetIds, queryRoot: fitResult?.queryRoot || null, rankingRoot: fitResult?.rankingRoot || null, })).slice(0, 16)}`; const sourceManifestRoot = firstString(input.sourceManifestRoot) || `sha256:${sha256(stableStringify({ assetPackId, - selectedCandidateAssetIds, + fitDepositAssetIds, withheld: 'protected_source_before_settlement', }))}`; const proofRoot = firstString(input.proofRoot) @@ -642,7 +746,7 @@ export function buildAssetPackSourceSafePreview(input: { assetPackId, needId: input.need.needId, fitResultState: fitResult?.resultState || 'blocked_readiness', - selectedCandidateAssetIds, + fitDepositAssetIds, feeQuoteRoot: feeQuote.quoteRoot, projectionRoot, sourceManifestRoot, @@ -663,8 +767,9 @@ export function buildAssetPackSourceSafePreview(input: { }, fit: { resultState: fitResult?.resultState || 'blocked_readiness', - resultReasons: fitResult?.resultReasons || ['Fit search has not produced worthy source-bound evidence.'], + resultReasons: fitResult?.resultReasons || ['Finding Fits has not produced worthy source-bound evidence.'], admittedFitQuality, + fitDepositAssetIds, selectedCandidateAssetIds, queryRoot: fitResult?.queryRoot || null, rankingRoot: fitResult?.rankingRoot || null, @@ -699,7 +804,8 @@ export function buildAssetPackSourceSafePreview(input: { visibleBeforeSettlement: [ 'need measurement', 'fit measurement', - 'selected candidate ids', + 'fit deposit ids', + 'selected candidate ids compatibility alias', 'roots', 'score band', 'proof posture', diff --git a/packages/pipelines/asset-pack/src/reading-pipeline-contract.ts b/packages/pipelines/asset-pack/src/reading-pipeline-contract.ts new file mode 100644 index 000000000..7c99e0947 --- /dev/null +++ b/packages/pipelines/asset-pack/src/reading-pipeline-contract.ts @@ -0,0 +1,632 @@ +export const READ_NEED_COMPREHENSION_SYNTHESIS = 'ReadNeedComprehensionSynthesis' as const; +export const READ_FINDING_FITS_SYNTHESIS = 'ReadFindingFitsSynthesis' as const; + +export type ReadingPipelineName = + | typeof READ_NEED_COMPREHENSION_SYNTHESIS + | typeof READ_FINDING_FITS_SYNTHESIS; + +export type ReadingPipelinePtrrStepName = 'plan' | 'try' | 'refine' | 'retry'; + +export type ReadingPipelineThricifiedFailsafe = + | 'prepare-concise-context' + | 'chunk-then-sum' + | 'stitch-until-complete'; + +export type ReadingPipelinePtrrStepKind = + | 'deterministic' + | 'model-structured' + | 'tool' + | 'review' + | 'settlement'; + +export type ReadingPipelinePromptContract = { + templateId: string; + template: string; + interpolatedContextKeys: string[]; +}; + +export type ReadingPipelineToolContract = { + toolId: string; + inputType: string; + outputType: string; +}; + +export type ReadingPipelineThricifiedGenerationContract = { + thricifiedGenerationId: string; + failsafe: ReadingPipelineThricifiedFailsafe; + reasonPromptId: string; + judgePromptId: string; + structuredOutputPromptId: string; + returnTypes: { + reason: 'Reasoning'; + judge: 'Judgment'; + structuredOutput: string; + }; + stores: string[]; + telemetry: string[]; +}; + +export type ReadingPipelinePtrrStepContract = { + ptrrStepName: ReadingPipelinePtrrStepName; + ptrrStepId: string; + purpose: string; + thricifiedGenerationIds: string[]; + thricifiedGenerations: ReadingPipelineThricifiedGenerationContract[]; + kind: ReadingPipelinePtrrStepKind; + prompt?: ReadingPipelinePromptContract; + tools: ReadingPipelineToolContract[]; + inputType: string; + outputType: string; + stores: string[]; + telemetry: string[]; +}; + +export type ReadingPipelineAgentContract = { + kind: 'ptrr-agent'; + agentId: string; + objectiveId: string; + returnType: string; + promptRegistry: { + factory: 'factoryAgentWithPTRR'; + carrier: 'prompt+stepPrompts'; + agentPromptId: string; + ptrrStepPromptIds: Record; + promptPartNamespaces: { + agent: 'agent/*'; + ptrrStep: 'ptrr/*/purpose'; + generation: 'generation:*'; + failsafe: 'failsafe:*'; + }; + }; + ptrrSteps: ReadingPipelinePtrrStepContract[]; +}; + +export type ReadingPipelinePhaseContract = { + phaseId: string; + stores: string[]; + agents: ReadingPipelineAgentContract[]; +}; + +export type ReadingPipelineContract = { + pipelineName: ReadingPipelineName; + purpose: string; + uxStepIds: string[]; + phases: ReadingPipelinePhaseContract[]; +}; + +export type ReadingPipelineContractSummary = { + pipelineName: ReadingPipelineName; + phaseCount: number; + agentCount: number; + ptrrAgentCount: number; + ptrrStepCount: number; + modelStructuredPtrrStepCount: number; + thricifiedGenerationCount: number; + toolCount: number; + returnTypes: string[]; +}; + +const READ_NEED_PROMPT_TEMPLATE = [ + 'You are the ReadNeedComprehensionSynthesis Need-framing agent.', + 'Synthesize exactly what the reader asked Bitcode to read, no more and no less.', + 'Return requirements, closure criteria, failure modes, target artifact kinds, source constraints, proof expectations, and pricing measurement inputs.', + 'Do not search deposits, reveal protected source, quote final settlement, or claim a fit before user Need acceptance.', +].join('\n'); + +const READ_FINDING_FITS_PROMPT_TEMPLATE = [ + 'You are the ReadFindingFitsSynthesis AssetPack synthesis agent.', + 'Use only the accepted Read-Need and source-bound depository evidence.', + 'Return worthy_fit, no_worthy_fit, or blocked_readiness with search, ranking, synthesis, validation, preview, and settlement-readiness evidence.', + 'Do not expose protected source before settlement and do not claim BTC/ledger finality without readback.', +].join('\n'); + +const readNeedTelemetry = (suffix: string) => `${READ_NEED_COMPREHENSION_SYNTHESIS}.telemetry.${suffix}`; +const readFitTelemetry = (suffix: string) => `${READ_FINDING_FITS_SYNTHESIS}.telemetry.${suffix}`; + +const PTRR_STEP_NAMES: ReadingPipelinePtrrStepName[] = ['plan', 'try', 'refine', 'retry']; +const THRICIFIED_FAILSAFES: ReadingPipelineThricifiedFailsafe[] = [ + 'prepare-concise-context', + 'chunk-then-sum', + 'stitch-until-complete', +]; + +const PTRR_STEP_PURPOSE: Record = { + plan: 'Plan the typed result and evidence path before attempting the agent objective.', + try: 'Attempt the agent objective with the registered prompt, tools, and typed output schema.', + refine: 'Judge and improve the attempted result against the objective, context, and schema.', + retry: 'Recover from failed or incomplete attempts and return the safest typed result.', +}; + +type PtrrAgentConfig = { + pipelineName: ReadingPipelineName; + phaseKey: string; + agentKey: string; + objectiveId: string; + kind: ReadingPipelinePtrrStepKind; + returnType: string; + inputType: string; + outputType?: string; + prompt?: ReadingPipelinePromptContract; + tools?: ReadingPipelineToolContract[]; + stores: string[]; + telemetry: string[]; +}; + +function ptrrPromptRegistry( + pipelineName: ReadingPipelineName, + phaseKey: string, + agentKey: string, +): ReadingPipelineAgentContract['promptRegistry'] { + const promptPrefix = `${pipelineName}.prompt.${phaseKey}.${agentKey}`; + return { + factory: 'factoryAgentWithPTRR', + carrier: 'prompt+stepPrompts', + agentPromptId: `${promptPrefix}.agent`, + ptrrStepPromptIds: { + plan: `${promptPrefix}.plan`, + try: `${promptPrefix}.try`, + refine: `${promptPrefix}.refine`, + retry: `${promptPrefix}.retry`, + }, + promptPartNamespaces: { + agent: 'agent/*', + ptrrStep: 'ptrr/*/purpose', + generation: 'generation:*', + failsafe: 'failsafe:*', + }, + }; +} + +function thricifiedGenerationsForPtrrStep( + config: PtrrAgentConfig, + ptrrStepName: ReadingPipelinePtrrStepName, + outputType: string, +): ReadingPipelineThricifiedGenerationContract[] { + return THRICIFIED_FAILSAFES.map((failsafe) => { + const baseId = `${config.pipelineName}.thricified-generation.${config.phaseKey}.${config.agentKey}.${ptrrStepName}.${failsafe}`; + const promptBaseId = `${config.pipelineName}.prompt.${config.phaseKey}.${config.agentKey}.${ptrrStepName}.${failsafe}`; + return { + thricifiedGenerationId: baseId, + failsafe, + reasonPromptId: `${promptBaseId}.reason`, + judgePromptId: `${promptBaseId}.judge`, + structuredOutputPromptId: `${promptBaseId}.structured-output`, + returnTypes: { + reason: 'Reasoning', + judge: 'Judgment', + structuredOutput: outputType, + }, + stores: [ + ...config.stores, + `${baseId}.reasoning`, + `${baseId}.judgment`, + `${baseId}.typed-output`, + ], + telemetry: [ + ...config.telemetry, + `${config.pipelineName}.telemetry.${config.phaseKey}.${config.agentKey}.${ptrrStepName}.${failsafe}.reasoning`, + `${config.pipelineName}.telemetry.${config.phaseKey}.${config.agentKey}.${ptrrStepName}.${failsafe}.judgment`, + `${config.pipelineName}.telemetry.${config.phaseKey}.${config.agentKey}.${ptrrStepName}.${failsafe}.typed-output`, + ], + }; + }); +} + +function ptrrAgent(config: PtrrAgentConfig): ReadingPipelineAgentContract { + const agentId = `${config.pipelineName}.${config.phaseKey}.${config.agentKey}`; + const outputType = config.outputType || config.returnType; + return { + kind: 'ptrr-agent', + agentId, + objectiveId: config.objectiveId, + returnType: config.returnType, + promptRegistry: ptrrPromptRegistry(config.pipelineName, config.phaseKey, config.agentKey), + ptrrSteps: PTRR_STEP_NAMES.map((ptrrStepName) => { + const thricifiedGenerations = thricifiedGenerationsForPtrrStep(config, ptrrStepName, outputType); + return { + ptrrStepName, + ptrrStepId: `${agentId}.${ptrrStepName}`, + purpose: PTRR_STEP_PURPOSE[ptrrStepName], + thricifiedGenerationIds: thricifiedGenerations.map((generation) => generation.thricifiedGenerationId), + thricifiedGenerations, + kind: config.kind, + prompt: config.prompt, + tools: ptrrStepName === 'try' ? config.tools || [] : [], + inputType: config.inputType, + outputType, + stores: config.stores, + telemetry: config.telemetry, + }; + }), + }; +} + +export const READ_NEED_COMPREHENSION_SYNTHESIS_CONTRACT: ReadingPipelineContract = { + pipelineName: READ_NEED_COMPREHENSION_SYNTHESIS, + purpose: + 'Turn an enterprise Read Request into a reviewed, measured, source-constrained Need before ReadFindingFitsSynthesis can run.', + uxStepIds: ['request-read', 'review-synthesized-need'], + phases: [ + { + phaseId: 'ReadNeedComprehensionSynthesis.request', + stores: ['read/request.raw', 'read/source-revision.input'], + agents: [ + ptrrAgent({ + pipelineName: READ_NEED_COMPREHENSION_SYNTHESIS, + phaseKey: 'request', + agentKey: 'normalize', + objectiveId: 'ReadNeedComprehensionSynthesis.request.normalize.objective', + kind: 'deterministic', + returnType: 'ReadNeedSourceInput', + inputType: 'TerminalReadRequest', + stores: ['read/request.normalized', 'read/source-revision.normalized'], + telemetry: [ + readNeedTelemetry('prompt-input'), + readNeedTelemetry('source-revision'), + readNeedTelemetry('target-artifact-kinds'), + ], + }), + ], + }, + { + phaseId: 'ReadNeedComprehensionSynthesis.comprehend', + stores: ['read/need.prompt-template', 'read/need.interpolated-context'], + agents: [ + ptrrAgent({ + pipelineName: READ_NEED_COMPREHENSION_SYNTHESIS, + phaseKey: 'comprehend', + agentKey: 'need-synthesizer', + objectiveId: 'ReadNeedComprehensionSynthesis.comprehend.need-synthesizer.objective', + kind: 'model-structured', + prompt: { + templateId: 'ReadNeedComprehensionSynthesis.prompt.need-synthesis', + template: READ_NEED_PROMPT_TEMPLATE, + interpolatedContextKeys: [ + 'read.prompt', + 'sourceConstraints', + 'targetArtifactKinds', + 'closureCriteria', + 'feedbackHistory', + ], + }, + returnType: 'ReadNeed', + inputType: 'ReadNeedSourceInput', + stores: ['read/need.current', 'read/need.measurementRoot', 'read/need.reviewState'], + telemetry: [ + readNeedTelemetry('prompt-template'), + readNeedTelemetry('interpolated-prompt'), + readNeedTelemetry('raw-model-response'), + readNeedTelemetry('parsed-typed-output'), + readNeedTelemetry('schema-name'), + ], + }), + ], + }, + { + phaseId: 'ReadNeedComprehensionSynthesis.measure', + stores: ['read/need.measurement-vector', 'read/need.pricing-inputs'], + agents: [ + ptrrAgent({ + pipelineName: READ_NEED_COMPREHENSION_SYNTHESIS, + phaseKey: 'measure', + agentKey: 'need-measurement', + objectiveId: 'ReadNeedComprehensionSynthesis.measure.need-measurement.objective', + kind: 'deterministic', + returnType: 'ReadNeedPricingMeasurementInputs', + inputType: 'ReadNeed', + stores: ['read/need.measurementVector', 'read/need.weightedRequestedVolume'], + telemetry: [ + readNeedTelemetry('measurement-vector'), + readNeedTelemetry('weighted-requested-volume'), + readNeedTelemetry('measurement-root'), + ], + }), + ], + }, + { + phaseId: 'ReadNeedComprehensionSynthesis.review', + stores: ['read/need.feedback-history', 'read/need.acceptance-root'], + agents: [ + ptrrAgent({ + pipelineName: READ_NEED_COMPREHENSION_SYNTHESIS, + phaseKey: 'review', + agentKey: 'operator-review', + objectiveId: 'ReadNeedComprehensionSynthesis.review.operator-review.objective', + kind: 'review', + returnType: 'ReadNeedReviewState', + inputType: 'ReadNeed', + outputType: 'AcceptedReadNeed | ResynthesisRequestedReadNeed', + stores: ['read/need.accepted', 'read/need.feedbackHistory', 'read/need.acceptanceRoot'], + telemetry: [ + readNeedTelemetry('review-state'), + readNeedTelemetry('accepted-at'), + readNeedTelemetry('acceptance-root'), + readNeedTelemetry('feedback-history'), + ], + }), + ], + }, + ], +}; + +export const READ_FINDING_FITS_SYNTHESIS_CONTRACT: ReadingPipelineContract = { + pipelineName: READ_FINDING_FITS_SYNTHESIS, + purpose: + 'Search the Bitcode Depository with an accepted Need, synthesize a source-safe AssetPack preview, and prepare settlement-bound delivery evidence.', + uxStepIds: ['request-fit', 'review-synthesized-asset-pack', 'buy-asset-pack-settle'], + phases: [ + { + phaseId: 'ReadFindingFitsSynthesis.admit', + stores: ['read/need.accepted', 'fit/admission'], + agents: [ + ptrrAgent({ + pipelineName: READ_FINDING_FITS_SYNTHESIS, + phaseKey: 'admit', + agentKey: 'accepted-need-gate', + objectiveId: 'ReadFindingFitsSynthesis.admit.accepted-need-gate.objective', + kind: 'deterministic', + returnType: 'ReadFindingFitsAdmission', + inputType: 'AcceptedReadNeed', + stores: ['fit/admission.result'], + telemetry: [ + readFitTelemetry('need-id'), + readFitTelemetry('measurement-root'), + readFitTelemetry('blockers'), + readFitTelemetry('admitted'), + ], + }), + ], + }, + { + phaseId: 'ReadFindingFitsSynthesis.prepare', + stores: ['setup.plan', 'setup/read-comprehension.model', 'setup/danger-wall.result'], + agents: [ + ptrrAgent({ + pipelineName: READ_FINDING_FITS_SYNTHESIS, + phaseKey: 'prepare', + agentKey: 'setup-plan', + objectiveId: 'ReadFindingFitsSynthesis.prepare.setup-plan.objective', + kind: 'model-structured', + prompt: { + templateId: 'ReadFindingFitsSynthesis.prompt.setup-plan', + template: + 'Produce one concise source-bound plan for the accepted Need to Finding Fits run. Do not claim settlement, delivery, or finality.', + interpolatedContextKeys: ['read', 'repository', 'sourceRevision', 'fitResult'], + }, + returnType: 'PlanSchema', + inputType: 'AcceptedReadNeed + SourceRevision', + stores: ['setup.plan', 'setup/plan.result'], + telemetry: [ + readFitTelemetry('prompt-template'), + readFitTelemetry('interpolated-prompt'), + readFitTelemetry('raw-model-response'), + readFitTelemetry('parsed-typed-output'), + ], + }), + ptrrAgent({ + pipelineName: READ_FINDING_FITS_SYNTHESIS, + phaseKey: 'prepare', + agentKey: 'read-comprehension', + objectiveId: 'ReadFindingFitsSynthesis.prepare.read-comprehension.objective', + kind: 'model-structured', + prompt: { + templateId: 'ReadFindingFitsSynthesis.prompt.read-comprehension', + template: + 'Translate the accepted Need and expressed Read into one auditable Read model for AssetPack synthesis. Return source-bound evidence only.', + interpolatedContextKeys: ['read', 'definitionOfRead', 'repository', 'sourceRevision', 'deposit', 'fitResult'], + }, + returnType: 'BoundedReadComprehensionSchema', + inputType: 'AcceptedReadNeed + DepositorySearchBaseline', + stores: ['setup/read-comprehension.model', 'setup/read-comprehension.riskAdmissionInput'], + telemetry: [ + readFitTelemetry('prompt-template'), + readFitTelemetry('interpolated-prompt'), + readFitTelemetry('raw-model-response'), + readFitTelemetry('parsed-typed-output'), + ], + }), + ], + }, + { + phaseId: 'ReadFindingFitsSynthesis.discovery', + stores: ['depository/search.result', 'fit/result'], + agents: [ + ptrrAgent({ + pipelineName: READ_FINDING_FITS_SYNTHESIS, + phaseKey: 'discovery', + agentKey: 'finding-fits', + objectiveId: 'ReadFindingFitsSynthesis.discovery.finding-fits.objective', + kind: 'tool', + tools: [ + { + toolId: 'ReadFindingFitsSynthesis.tool.lexical-depository-search', + inputType: 'DepositorySearchRead', + outputType: 'DepositorySearchResult', + }, + { + toolId: 'ReadFindingFitsSynthesis.tool.vector-depository-search', + inputType: 'EmbeddingSearchRequest', + outputType: 'EmbeddingSearchResult', + }, + ], + returnType: 'DepositoryFitsResult', + inputType: 'AcceptedReadNeed', + stores: [ + 'depository/search.result', + 'depository/search.queryRoot', + 'depository/search.rankingRoot', + 'fit/deposits', + 'fit/depositAssetIds', + ], + telemetry: [ + readFitTelemetry('tool-input'), + readFitTelemetry('tool-output'), + readFitTelemetry('query-root'), + readFitTelemetry('ranking-root'), + readFitTelemetry('fit-deposit-ranking'), + ], + }), + ], + }, + { + phaseId: 'ReadFindingFitsSynthesis.implementation', + stores: ['implementation.assetPack', 'implementation.assetPackSynthesisArtifacts'], + agents: [ + ptrrAgent({ + pipelineName: READ_FINDING_FITS_SYNTHESIS, + phaseKey: 'implementation', + agentKey: 'asset-pack', + objectiveId: 'ReadFindingFitsSynthesis.implementation.asset-pack.objective', + kind: 'model-structured', + prompt: { + templateId: 'ReadFindingFitsSynthesis.prompt.asset-pack-synthesis', + template: READ_FINDING_FITS_PROMPT_TEMPLATE, + interpolatedContextKeys: [ + 'acceptedReadNeed', + 'depositorySearchResult', + 'fitDepositAssetIds', + 'fitDeposits', + 'sourceRevision', + 'deliveryMechanismTemplate', + ], + }, + returnType: 'AssetPackSynthesisOutput', + inputType: 'AcceptedReadNeed + DepositoryFitsResult', + stores: ['implementation.assetPack', 'implementation.writtenAssets'], + telemetry: [ + readFitTelemetry('prompt-template'), + readFitTelemetry('interpolated-prompt'), + readFitTelemetry('raw-model-response'), + readFitTelemetry('parsed-typed-output'), + readFitTelemetry('usage'), + ], + }), + ], + }, + { + phaseId: 'ReadFindingFitsSynthesis.validate', + stores: ['validation.discovery.issues', 'validation.implementation.issues', 'validation.readyToFinish'], + agents: [ + ptrrAgent({ + pipelineName: READ_FINDING_FITS_SYNTHESIS, + phaseKey: 'validate', + agentKey: 'fit-quality', + objectiveId: 'ReadFindingFitsSynthesis.validate.fit-quality.objective', + kind: 'model-structured', + prompt: { + templateId: 'ReadFindingFitsSynthesis.prompt.fit-quality-validation', + template: + 'Validate the source-bound AssetPack synthesized from qualifying fit deposits against the accepted Need, search proof, disclosure policy, and finish-readiness gates.', + interpolatedContextKeys: [ + 'acceptedReadNeed', + 'depositorySearchResult', + 'assetPackSynthesisOutput', + 'sourceSafePreview', + ], + }, + tools: [ + { + toolId: 'ReadFindingFitsSynthesis.tool.verification-evidence', + inputType: 'AssetPackVerificationEvidenceInput', + outputType: 'AssetPackVerificationEvidenceResult', + }, + ], + returnType: 'ReadyToFinishOutput', + inputType: 'AssetPackSynthesisOutput', + stores: ['validation.readyToFinish', 'validation.selfInstruction'], + telemetry: [ + readFitTelemetry('prompt-template'), + readFitTelemetry('interpolated-prompt'), + readFitTelemetry('raw-model-response'), + readFitTelemetry('validation-issues'), + readFitTelemetry('tool-input'), + readFitTelemetry('tool-output'), + readFitTelemetry('parsed-typed-output'), + ], + }), + ], + }, + { + phaseId: 'ReadFindingFitsSynthesis.preview', + stores: ['asset-pack/preview.sourceSafe', 'asset-pack/preview.feeQuote'], + agents: [ + ptrrAgent({ + pipelineName: READ_FINDING_FITS_SYNTHESIS, + phaseKey: 'preview', + agentKey: 'source-safe-preview', + objectiveId: 'ReadFindingFitsSynthesis.preview.source-safe-preview.objective', + kind: 'deterministic', + returnType: 'AssetPackSourceSafePreview', + inputType: 'ReadNeed + DepositoryFitResultEvidence', + stores: ['asset-pack/preview.sourceSafe', 'asset-pack/preview.feeQuote'], + telemetry: [ + readFitTelemetry('fee-quote'), + readFitTelemetry('range-projection'), + readFitTelemetry('disclosure-policy'), + readFitTelemetry('access-policy'), + ], + }), + ], + }, + { + phaseId: 'ReadFindingFitsSynthesis.settle', + stores: ['ledger.settlement', 'finish.deliveryMechanism', 'finish.asset_pack_completion'], + agents: [ + ptrrAgent({ + pipelineName: READ_FINDING_FITS_SYNTHESIS, + phaseKey: 'settle', + agentKey: 'buy-deliver', + objectiveId: 'ReadFindingFitsSynthesis.settle.buy-deliver.objective', + kind: 'settlement', + tools: [ + { + toolId: 'ReadFindingFitsSynthesis.tool.vcs-create-pull-request', + inputType: 'PullRequestDeliveryInput', + outputType: 'PullRequestDeliveryResult', + }, + ], + returnType: 'AssetPackCompletionOutput', + inputType: 'AssetPackSourceSafePreview', + stores: ['ledger.settlement', 'finish.pullRequestUrl', 'finish.asset_pack_completion.result'], + telemetry: [ + readFitTelemetry('btc-fee-receipt'), + readFitTelemetry('range-readback'), + readFitTelemetry('license-readback'), + readFitTelemetry('journal-entry-ids'), + readFitTelemetry('pull-request'), + ], + }), + ], + }, + ], +}; + +export const READING_PIPELINE_CONTRACTS = [ + READ_NEED_COMPREHENSION_SYNTHESIS_CONTRACT, + READ_FINDING_FITS_SYNTHESIS_CONTRACT, +] as const; + +export function summarizeReadingPipelineContract( + contract: ReadingPipelineContract, +): ReadingPipelineContractSummary { + const agents = contract.phases.flatMap((phase) => phase.agents); + const steps = agents.flatMap((agent) => agent.ptrrSteps); + const thricifiedGenerations = steps.flatMap((step) => step.thricifiedGenerations); + const tools = steps.flatMap((step) => step.tools); + const returnTypes = [...new Set(agents.map((agent) => agent.returnType).concat(steps.map((step) => step.outputType)))].sort(); + return { + pipelineName: contract.pipelineName, + phaseCount: contract.phases.length, + agentCount: agents.length, + ptrrAgentCount: agents.filter((agent) => agent.kind === 'ptrr-agent').length, + ptrrStepCount: steps.length, + modelStructuredPtrrStepCount: steps.filter((step) => step.kind === 'model-structured').length, + thricifiedGenerationCount: thricifiedGenerations.length, + toolCount: tools.length, + returnTypes, + }; +} + +export function listReadingPipelineContractSummaries(): ReadingPipelineContractSummary[] { + return READING_PIPELINE_CONTRACTS.map((contract) => summarizeReadingPipelineContract(contract)); +} diff --git a/packages/pipelines/asset-pack/src/tools/AssetPackLexicalDepositorySearchTool.ts b/packages/pipelines/asset-pack/src/tools/AssetPackLexicalDepositorySearchTool.ts index a7385475f..f8f8a47af 100644 --- a/packages/pipelines/asset-pack/src/tools/AssetPackLexicalDepositorySearchTool.ts +++ b/packages/pipelines/asset-pack/src/tools/AssetPackLexicalDepositorySearchTool.ts @@ -12,10 +12,10 @@ const LEXICAL_DEPOSITORY_SEARCH_DOC_CODE_TOOL_PROMPT = { format() { return [ 'Tool: lexical-depository-search', - 'Purpose: read back or rerun Bitcode depository candidate recall for the current Read/Fit execution.', + 'Purpose: read back or rerun Bitcode depository Finding Fits discovery for the current Read/Fit pipeline run.', 'Inputs: optional read, repositoryFullName, sourceBranch, sourceCommit, queryTerms, assets, or depositoryAssets.', - 'Output: depository search state, selected candidate ids, query/ranking roots, slim ranking evidence, and fit result evidence.', - 'Use when a PTRR agent needs to verify the already computed Read/Fit depository search result before AssetPack synthesis.', + 'Output: depository search state, fit deposit ids, query/ranking roots, slim ranking evidence, and fit result evidence.', + 'Use when a PTRR agent needs to verify the already computed Finding Fits depository result before AssetPack synthesis.', ].join('\n'); }, }; @@ -52,14 +52,29 @@ function readPipelineInput(execution: any): Record { } function summarizeSearch(result: DepositorySearchResult): Record { + const fitDeposits = Array.isArray((result as any).fitDeposits) + ? (result as any).fitDeposits + : result.selectedCandidates || []; + const selectedCandidates = result.selectedCandidates || fitDeposits; + const blockedCandidates = result.blockedCandidates || []; + const candidateRanking = result.candidateRanking || selectedCandidates; + const fitDepositAssetIds = result.fitDepositAssetIds || result.selectedCandidateAssetIds || []; let fitResult: unknown; try { - fitResult = buildDepositoryFitResultEvidence(result); + fitResult = buildDepositoryFitResultEvidence({ + ...result, + fitDeposits, + selectedCandidates, + blockedCandidates, + candidateRanking, + fitDepositAssetIds, + } as DepositorySearchResult); } catch { fitResult = { schema: 'bitcode.asset-pack.fit-result', resultState: result.resultState, resultReasons: result.resultReasons, + fitDepositAssetIds, selectedCandidateAssetIds: result.selectedCandidateAssetIds, queryRoot: result.queryRoot, rankingRoot: result.rankingRoot, @@ -74,12 +89,14 @@ function summarizeSearch(result: DepositorySearchResult): Record { - const selectedCandidates = result.selectedCandidates.map(summarizeCandidate); - const blockedCandidates = result.blockedCandidates.map(summarizeCandidate); - const candidateRanking = result.candidateRanking.slice(0, 10).map(summarizeCandidate); - const selectedVerification = result.selectedCandidates.map((candidate) => candidate.verification); + const fitDepositSource: DepositoryCandidate[] = Array.isArray((result as any).fitDeposits) + ? (result as any).fitDeposits as DepositoryCandidate[] + : result.selectedCandidates || []; + const blockedCandidateSource: DepositoryCandidate[] = result.blockedCandidates || []; + const rankingSource: DepositoryCandidate[] = result.candidateRanking || fitDepositSource; + const fitDeposits = fitDepositSource.map(summarizeCandidate); + const blockedCandidates = blockedCandidateSource.map(summarizeCandidate); + const fitDepositRanking = rankingSource.slice(0, 10).map(summarizeCandidate); + const fitDepositVerification = fitDepositSource.map((deposit: DepositoryCandidate) => deposit.verification); const allVerification = [ - ...result.selectedCandidates, - ...result.blockedCandidates, - ...result.rejectedCandidates, - ].map((candidate) => candidate.verification); + ...fitDepositSource, + ...blockedCandidateSource, + ...(result.rejectedCandidates || []), + ].map((deposit: DepositoryCandidate) => deposit.verification); return { schema: 'bitcode.asset-pack.verification-evidence-tool-result', resultState: result.resultState, resultReasons: result.resultReasons, searchedAssetCount: result.searchedAssetCount, + fitDepositAssetIds: result.fitDepositAssetIds || result.selectedCandidateAssetIds || [], selectedCandidateAssetIds: result.selectedCandidateAssetIds, queryRoot: result.queryRoot, rankingRoot: result.rankingRoot, embeddingPolicy: result.embeddingPolicy, readiness: { - selectedCandidateCount: result.selectedCandidates.length, - blockedCandidateCount: result.blockedCandidates.length, - settlementEligibleCandidateCount: result.selectedCandidates.filter( - (candidate) => candidate.useTier === 'settlement-eligible', + fitDepositCount: fitDepositSource.length, + selectedCandidateCount: (result.selectedCandidates || fitDepositSource).length, + blockedFitDepositCount: blockedCandidateSource.length, + settlementEligibleFitDepositCount: fitDepositSource.filter( + (deposit) => deposit.useTier === 'settlement-eligible', ).length, - sourceBoundCandidateCount: result.selectedCandidates.filter( - (candidate) => candidate.verification?.repositoryBound && candidate.verification?.sourceRevisionBound, + sourceBoundFitDepositCount: fitDepositSource.filter( + (deposit) => deposit.verification?.repositoryBound && deposit.verification?.sourceRevisionBound, ).length, - proofReadyCandidateCount: result.selectedCandidates.filter( - (candidate) => candidate.verification?.hasWalletOrAttestationProof && candidate.verification?.proofRootPresent, + proofReadyFitDepositCount: fitDepositSource.filter( + (deposit) => deposit.verification?.hasWalletOrAttestationProof && deposit.verification?.proofRootPresent, ).length, - measurementReadyCandidateCount: result.selectedCandidates.filter( - (candidate) => candidate.verification?.hasAssetMeasurementEvidence, + measurementReadyFitDepositCount: fitDepositSource.filter( + (deposit) => deposit.verification?.hasAssetMeasurementEvidence, ).length, - readbackReadyCandidateCount: result.selectedCandidates.filter( - (candidate) => - !candidate.verification?.reconciliationReadbackRequired || - candidate.verification?.reconciliationReadbackPresent, + readbackReadyFitDepositCount: fitDepositSource.filter( + (deposit) => + !deposit.verification?.reconciliationReadbackRequired || + deposit.verification?.reconciliationReadbackPresent, ).length, - selectedWarnings: unique(selectedVerification.map((verification) => verification?.warnings)), - selectedBlockers: unique(selectedVerification.map((verification) => verification?.blockers)), + settlementEligibleCandidateCount: fitDepositSource.filter( + (deposit) => deposit.useTier === 'settlement-eligible', + ).length, + sourceBoundCandidateCount: fitDepositSource.filter( + (deposit) => deposit.verification?.repositoryBound && deposit.verification?.sourceRevisionBound, + ).length, + proofReadyCandidateCount: fitDepositSource.filter( + (deposit) => deposit.verification?.hasWalletOrAttestationProof && deposit.verification?.proofRootPresent, + ).length, + measurementReadyCandidateCount: fitDepositSource.filter( + (deposit) => deposit.verification?.hasAssetMeasurementEvidence, + ).length, + readbackReadyCandidateCount: fitDepositSource.filter( + (deposit) => + !deposit.verification?.reconciliationReadbackRequired || + deposit.verification?.reconciliationReadbackPresent, + ).length, + selectedWarnings: unique(fitDepositVerification.map((verification) => verification?.warnings)), + selectedBlockers: unique(fitDepositVerification.map((verification) => verification?.blockers)), + fitDepositWarnings: unique(fitDepositVerification.map((verification) => verification?.warnings)), + fitDepositBlockers: unique(fitDepositVerification.map((verification) => verification?.blockers)), allWarnings: unique(allVerification.map((verification) => verification?.warnings)), allBlockers: unique(allVerification.map((verification) => verification?.blockers)), }, - selectedCandidates, + fitDeposits, + selectedCandidates: fitDeposits, blockedCandidates, - candidateRanking, + fitDepositRanking, + candidateRanking: fitDepositRanking, }; } diff --git a/packages/protocol/src/bitcode-demo.js b/packages/protocol/src/bitcode-demo.js index 3848fb092..ad965a788 100644 --- a/packages/protocol/src/bitcode-demo.js +++ b/packages/protocol/src/bitcode-demo.js @@ -5891,7 +5891,7 @@ export function runMakeBitcodeBranch(state, input = {}) { decisionMode: input.readReviewDecisionMode || input.decisionMode || 'deterministic-fifth-gate-local-review' }); if (!readReview.fitSearchAdmission?.admitted) { - throw new Error('Bitcode fit search cannot proceed before the measured Read is accepted for source-to-shares review.'); + throw new Error('Bitcode Finding Fits cannot proceed before the measured Read is accepted for source-to-shares review.'); } const reviewedReadMeasurement = { ...readMeasurement, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e43029306..cf11af1d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2436,6 +2436,9 @@ importers: '@aws-sdk/client-s3': specifier: ^3.408.0 version: 3.840.0 + '@bitcode/btd': + specifier: workspace:* + version: link:../packages/btd '@bitcode/email': specifier: workspace:* version: link:../packages/email @@ -2448,6 +2451,9 @@ importers: '@bitcode/observability': specifier: workspace:* version: link:../packages/observability + '@bitcode/pipeline-asset-pack': + specifier: workspace:* + version: link:../packages/pipelines/asset-pack '@bitcode/pipeline-hosts': specifier: workspace:* version: link:../packages/pipeline-hosts diff --git a/uapi/README.md b/uapi/README.md index 20a984996..9ae2c1c86 100644 --- a/uapi/README.md +++ b/uapi/README.md @@ -41,6 +41,10 @@ Contained auxillary rails should converge on the shared panel-plus-tabs carriers - `app/terminal/` `/terminal` route-local composition, route-owned transaction query state, flow-guide/runtime bridge, Deposit/Read workbench, and transaction detail. + Gate 7 operations readiness is rendered by + `app/terminal/TerminalOperationalHealthPanel.tsx` from the + `@bitcode/btd/terminal-operational-health` package read; do not replace it + with UI-only lane or telemetry fixtures. - `app/exchange/` Deferred Exchange activity source retained for V35+ work. - `app/auxillaries/` diff --git a/uapi/app/(root)/components/MarketingFeaturesGrid.tsx b/uapi/app/(root)/components/MarketingFeaturesGrid.tsx index fe16d121a..4c6036310 100644 --- a/uapi/app/(root)/components/MarketingFeaturesGrid.tsx +++ b/uapi/app/(root)/components/MarketingFeaturesGrid.tsx @@ -265,7 +265,7 @@ const MarketingFeaturesGrid = () => {

Measured Read

- Bitcode synthesizes a reviewable Read from repository evidence before any fit search or AssetPack settlement begins. + Bitcode synthesizes a reviewable Read from repository evidence before any Finding Fits or AssetPack settlement begins.

diff --git a/uapi/app/(root)/components/MarketingMarketplaceSection.tsx b/uapi/app/(root)/components/MarketingMarketplaceSection.tsx index 0b4df8b88..378dd8122 100644 --- a/uapi/app/(root)/components/MarketingMarketplaceSection.tsx +++ b/uapi/app/(root)/components/MarketingMarketplaceSection.tsx @@ -674,7 +674,7 @@ export default function MarketingMarketplaceSection({ disableTickerFetch = false { icon: BanknotesIcon, title: 'Granular Budgets', - body: 'Topical procurement allowances can help fit search decide when paid knowledge is worth the spend.', + body: 'Topical procurement allowances can help Finding Fits decide when paid knowledge is worth the spend.', }, { icon: ChartBarIcon, diff --git a/uapi/app/api/read-review/route.ts b/uapi/app/api/read-review/route.ts index b25b57a06..4c846d4bb 100644 --- a/uapi/app/api/read-review/route.ts +++ b/uapi/app/api/read-review/route.ts @@ -17,6 +17,7 @@ export async function GET(request: Request) { }), ); } catch (error) { + console.error(error); return toBitcodeErrorResponse(error); } } @@ -26,16 +27,38 @@ export async function POST(request: Request) { const body = await readBitcodeRequestBody(request); const action = readNeedAction(body); if (action === 'synthesize_read_need' || action === 'resynthesize_read_need') { - const { synthesizeReadNeedForPipelineInput } = await import('@bitcode/pipeline-asset-pack/read-need'); - const readNeed = synthesizeReadNeedForPipelineInput(readNeedPipelineInput(body)); + const { synthesizeReadNeedForPipelineInputWithInference } = await import('@bitcode/pipeline-asset-pack/read-need'); + const { + READ_NEED_COMPREHENSION_SYNTHESIS, + READ_NEED_COMPREHENSION_SYNTHESIS_CONTRACT, + summarizeReadingPipelineContract, + } = await import('@bitcode/pipeline-asset-pack/reading-pipeline-contract'); + const inferenceCapture = createRouteInferenceCapture(); + const readNeed = await synthesizeReadNeedForPipelineInputWithInference( + readNeedPipelineInput(body), + inferenceCapture.execution, + ); + const synthesisStep = + READ_NEED_COMPREHENSION_SYNTHESIS_CONTRACT.phases + .flatMap((phase) => phase.agents) + .flatMap((agent) => agent.ptrrSteps) + .find((step) => step.ptrrStepId === 'ReadNeedComprehensionSynthesis.comprehend.need-synthesizer.try'); return NextResponse.json({ ok: true, - stage: 'read_need_review', + pipelineName: READ_NEED_COMPREHENSION_SYNTHESIS, + stage: 'review_synthesized_need', action, readNeed, telemetry: { schema: 'bitcode.read-need.synthesis-telemetry', + pipelineName: READ_NEED_COMPREHENSION_SYNTHESIS, + pipelineContract: summarizeReadingPipelineContract(READ_NEED_COMPREHENSION_SYNTHESIS_CONTRACT), + phaseId: 'ReadNeedComprehensionSynthesis.comprehend', + agentId: 'ReadNeedComprehensionSynthesis.comprehend.need-synthesizer', + ptrrStepId: synthesisStep?.ptrrStepId || 'ReadNeedComprehensionSynthesis.comprehend.need-synthesizer.try', + thricifiedGenerationIds: synthesisStep?.thricifiedGenerationIds || [], + promptTemplate: synthesisStep?.prompt || null, promptInput: readNeed.read, interpolatedContext: { sourceConstraints: readNeed.sourceConstraints, @@ -50,43 +73,77 @@ export async function POST(request: Request) { failureModes: readNeed.failureModes, proofExpectations: readNeed.proofExpectations, }, + rawModelResponse: { + runtime: 'structured-read-need-synthesis', + outputKind: 'typed-protocol-synthesis', + content: readNeed, + }, + thricifiedGeneration: { + mode: inferenceCapture.value('bounded-inference', 'mode') || 'deterministic-typed-witness', + status: inferenceCapture.value('bounded-inference', 'status') || 'deterministic-fallback', + provider: inferenceCapture.value('bounded-inference', 'provider') || null, + reasoningOutput: inferenceCapture.value('llm', 'reasoningOutput') || null, + judgmentOutput: inferenceCapture.value('llm', 'judgmentOutput') || null, + structuredOutput: inferenceCapture.value('llm', 'parsedOutput') || null, + }, + parsedTypedOutput: readNeed, + returnType: 'ReadNeed', parsedNeed: readNeed, measurementRoot: readNeed.measurementRoot, reviewState: readNeed.reviewState, resynthesisAttempt: action === 'resynthesize_read_need', }, - nextProtocolAction: 'Review and accept the synthesized Read-Need before Need-Fit search.', + nextProtocolAction: 'Review and accept the synthesized Read-Need before Finding Fits.', }); } if (action === 'accept_read_need') { - const { acceptReadNeed, admitNeedFitSearch } = await import('@bitcode/pipeline-asset-pack/read-need'); + const { acceptReadNeed, admitReadFindingFits } = await import('@bitcode/pipeline-asset-pack/read-need'); + const { + READ_NEED_COMPREHENSION_SYNTHESIS, + READ_NEED_COMPREHENSION_SYNTHESIS_CONTRACT, + READ_FINDING_FITS_SYNTHESIS, + } = await import('@bitcode/pipeline-asset-pack/reading-pipeline-contract'); const readNeed = objectValue(body.readNeed) || objectValue(body.acceptedReadNeed); if (!readNeed || readNeed.schema !== 'bitcode.read.need') { return NextResponse.json({ error: 'readNeed is required' }, { status: 400 }); } const acceptedReadNeed = acceptReadNeed(readNeed as unknown as Parameters[0]); - const fitSearchAdmission = admitNeedFitSearch({ + const findingFitsAdmission = admitReadFindingFits({ acceptedReadNeed, requireAcceptedReadNeed: true, }); + const acceptanceStep = + READ_NEED_COMPREHENSION_SYNTHESIS_CONTRACT.phases + .flatMap((phase) => phase.agents) + .flatMap((agent) => agent.ptrrSteps) + .find((step) => step.ptrrStepId === 'ReadNeedComprehensionSynthesis.review.operator-review.try'); return NextResponse.json({ ok: true, - stage: 'need_fit_ready', + pipelineName: READ_NEED_COMPREHENSION_SYNTHESIS, + nextPipelineName: READ_FINDING_FITS_SYNTHESIS, + stage: 'request_fit_ready', action, acceptedReadNeed, readNeed: acceptedReadNeed, - fitSearchAdmission, + findingFitsAdmission, telemetry: { schema: 'bitcode.read-need.acceptance-telemetry', + pipelineName: READ_NEED_COMPREHENSION_SYNTHESIS, + phaseId: 'ReadNeedComprehensionSynthesis.review', + agentId: 'ReadNeedComprehensionSynthesis.review.operator-review', + ptrrStepId: acceptanceStep?.ptrrStepId || 'ReadNeedComprehensionSynthesis.review.operator-review.try', + thricifiedGenerationIds: acceptanceStep?.thricifiedGenerationIds || [], needId: acceptedReadNeed.needId, measurementRoot: acceptedReadNeed.measurementRoot, reviewState: acceptedReadNeed.reviewState, acceptanceRoot: acceptedReadNeed.review?.acceptanceRoot || null, - nextStage: acceptedReadNeed.review?.nextStage || 'need_fit_search', + nextStage: acceptedReadNeed.review?.nextStage || 'finding_fits', + nextPipelineName: READ_FINDING_FITS_SYNTHESIS, + returnType: 'AcceptedReadNeed', }, - nextProtocolAction: 'Run Need-Fit search with the accepted Read-Need.', + nextProtocolAction: 'Run Finding Fits with the accepted Read-Need.', }); } @@ -110,6 +167,47 @@ function readNeedAction(body: Record): string | null { return stringValue(body.action) || stringValue(body.readNeedAction); } +function createRouteInferenceCapture() { + const values = new Map(); + type CapturedExecution = { + child: (name?: string) => CapturedExecution; + getRoot: () => CapturedExecution; + store: (namespace: string, key: string, value: unknown) => void; + get: (namespace: string, key: string) => unknown; + findUp: (namespace: string, key: string) => unknown; + llms: { getDefaultLLM: () => undefined }; + }; + const execution: CapturedExecution = { + child() { + return execution; + }, + getRoot() { + return execution; + }, + store(namespace: string, key: string, value: unknown) { + values.set(`${namespace}:${key}`, value); + }, + get(namespace: string, key: string) { + return values.get(`${namespace}:${key}`); + }, + findUp(namespace: string, key: string) { + return values.get(`${namespace}:${key}`); + }, + llms: { + getDefaultLLM() { + return undefined; + }, + }, + }; + + return { + execution, + value(namespace: string, key: string) { + return values.get(`${namespace}:${key}`); + }, + }; +} + function readNeedPipelineInput(body: Record) { const readRequest = objectValue(body.readRequest); const read = objectValue(body.read); diff --git a/uapi/app/docs/bitcode-docs-content.ts b/uapi/app/docs/bitcode-docs-content.ts index d9f29abb0..61445fcd1 100644 --- a/uapi/app/docs/bitcode-docs-content.ts +++ b/uapi/app/docs/bitcode-docs-content.ts @@ -1422,9 +1422,9 @@ export const TERMINAL_ACTION_GUIDES = [ action: 'Accept, reject, or remeasure Read', location: 'Read measurement', write: - 'Choose whether the measured Read is admitted for fit search, rejected, or sent back for remeasurement with feedback.', + 'Choose whether the measured Read is admitted for Finding Fits, rejected, or sent back for remeasurement with feedback.', expectedRead: - 'Fit search stays blocked until Read review is accepted, and the closure map shows the current review posture.', + 'Finding Fits stays blocked until Read review is accepted, and the closure map shows the current review posture.', proofSignal: TERMINAL_WORKSPACE_EXPLAINERS.readScenarios.summary, }, { diff --git a/uapi/app/terminal/TerminalDepositReadWorkbench.tsx b/uapi/app/terminal/TerminalDepositReadWorkbench.tsx index cd0a096ca..aae3a2896 100644 --- a/uapi/app/terminal/TerminalDepositReadWorkbench.tsx +++ b/uapi/app/terminal/TerminalDepositReadWorkbench.tsx @@ -20,8 +20,10 @@ import type { TerminalRepositoryContextState } from './terminal-repository-conte import { buildLiveTerminalDepositReadWorkbenchSnapshot, normalizeTerminalDepositReadWorkbench, + TERMINAL_ENTERPRISE_READING_STEPS, type TerminalDepositedSourceRevision, type TerminalDepositReadWorkbench as TerminalDepositReadWorkbenchState, + type TerminalEnterpriseReadingStepId, } from './terminal-deposit-read-workbench'; import { buildTerminalFitPipelineHarnessStreamSnapshot, @@ -73,14 +75,7 @@ function terminalReadNeed(value: unknown): TerminalReadNeedState | null { } type ReadFitProgressState = 'draft' | 'measured' | 'admitted' | 'fit-recorded'; -type ReadingStageId = - | 'read-request' - | 'read-need-review' - | 'need-fit-search' - | 'asset-pack-preview' - | 'btc-settlement' - | 'unlock' - | 'pull-request-delivery'; +type ReadingStageId = TerminalEnterpriseReadingStepId; type TerminalReadNeedState = Record & { schema?: 'bitcode.read.need'; @@ -171,8 +166,8 @@ export default function TerminalDepositReadWorkbench({ : readFitProgress === 'draft' ? 'Record Read before admitting' : readFitProgress === 'measured' - ? 'Admit measured Read for fit search' - : 'Read admitted for fit search'; + ? 'Admit measured Read for Finding Fits' + : 'Read admitted for Finding Fits'; const fitResultActionLabel = recordingKey === 'fit' ? 'Recording fit...' @@ -181,10 +176,10 @@ export default function TerminalDepositReadWorkbench({ : 'Record fit result posture'; const liveFitActionLabel = harnessState === 'running' - ? 'Running live fit...' + ? 'Running Finding Fits...' : harnessState === 'completed' - ? 'Run live fit again' - : 'Run live AssetPack fit'; + ? 'Request Fit again' + : 'Request Fit'; const harnessReadActivityId = recordedAdmittedReadActivityId || admittedReadActivityId; const harnessRequestState = useMemo( () => @@ -286,18 +281,18 @@ export default function TerminalDepositReadWorkbench({ const settledReadback = ledgerSettlement?.status === 'settled'; const pullRequestDelivered = settledReadback && Boolean(textValue(objectValue(sourceSafePreview?.delivery)?.pullRequestTarget)); const activeReadingStage: ReadingStageId = pullRequestDelivered - ? 'pull-request-delivery' + ? 'buy-asset-pack-settle' : settledReadback - ? 'unlock' + ? 'buy-asset-pack-settle' : sourceSafePreview - ? 'asset-pack-preview' + ? 'review-synthesized-asset-pack' : harnessState === 'running' || (acceptedReadNeed && harnessState !== 'idle') - ? 'need-fit-search' + ? 'request-fit' : acceptedReadNeed - ? 'need-fit-search' + ? 'request-fit' : readNeed - ? 'read-need-review' - : 'read-request'; + ? 'review-synthesized-need' + : 'request-read'; const currentReadNeed = acceptedReadNeed || readNeed; const readNeedRows = useMemo(() => { if (!currentReadNeed) return []; @@ -314,15 +309,7 @@ export default function TerminalDepositReadWorkbench({ { label: 'Feedback turns', value: String(stringList(currentReadNeed.feedbackHistory).length) }, ]; }, [currentReadNeed]); - const stageCards: Array<{ id: ReadingStageId; label: string; detail: string }> = [ - { id: 'read-request', label: '1. Read Request', detail: 'Repository, branch, commit, and reader request are framed.' }, - { id: 'read-need-review', label: '2. Read-Need', detail: 'Need requirements, measurements, proof expectations, and feedback are reviewable.' }, - { id: 'need-fit-search', label: '3. Need-Fit', detail: 'Only the accepted Need can search deposited source and synthesize a fit.' }, - { id: 'asset-pack-preview', label: '4. Preview', detail: 'Measurements, roots, score posture, fee quote, and disclosure policy are visible.' }, - { id: 'btc-settlement', label: '5. BTC settlement', detail: 'Reader fee authorization and settlement readback gate source unlock.' }, - { id: 'unlock', label: '6. Unlock', detail: 'Owner or licensed read rights unlock only after range and license readback.' }, - { id: 'pull-request-delivery', label: '7. PR delivery', detail: 'The purchased AssetPack is delivered to the reading repository.' }, - ]; + const stageCards = TERMINAL_ENTERPRISE_READING_STEPS; const canRunLiveFit = !showDemonstrationWorkbench && recordingKey === null && @@ -369,7 +356,7 @@ export default function TerminalDepositReadWorkbench({ ); setReadFitProgress('measured'); setRecordMessage( - 'Measured Read recorded. Next admit it for source-bound fit search, or stop if the Read is too broad or unrelated to the deposited source.', + 'Measured Read recorded. Next admit it for source-bound Finding Fits, or stop if the Read is too broad or unrelated to the deposited source.', ); } else { await onRecordActivity(buildTerminalFitWorkbenchDraft(workbench)); @@ -396,10 +383,10 @@ export default function TerminalDepositReadWorkbench({ setRecordedAdmittedReadActivityId(recordActivityId(recorded)); setReadFitProgress('admitted'); setRecordMessage( - 'Measured Read admitted for fit search. Next run or record the fit result posture as worthy_fit, no_worthy_fit, or blocked_readiness evidence.', + 'Measured Read admitted for Finding Fits. Next run or record the fit result posture as worthy_fit, no_worthy_fit, or blocked_readiness evidence.', ); } catch (error) { - setRecordMessage(error instanceof Error ? error.message : 'Unable to admit the measured Read for fit search.'); + setRecordMessage(error instanceof Error ? error.message : 'Unable to admit the measured Read for Finding Fits.'); } finally { setRecordingKey(null); } @@ -444,7 +431,7 @@ export default function TerminalDepositReadWorkbench({ setReadNeedSynthesisCount((count) => count + 1); setReadNeedMessage( action === 'synthesize_read_need' - ? 'Read-Need synthesized for review before Need-Fit search.' + ? 'Read-Need synthesized for review before Finding Fits.' : 'Read-Need resynthesized with feedback for review.', ); } catch (error) { @@ -483,11 +470,11 @@ export default function TerminalDepositReadWorkbench({ } setAcceptedReadNeed(accepted); setReadNeed(accepted); - setReadNeedMessage('Read-Need accepted. Need-Fit search can now run against deposited source.'); + setReadNeedMessage('Read-Need accepted. Finding Fits can now run against deposited source.'); await onRecordActivity?.({ type: 'agentic-execution:read-measurement', detailSection: 'activity', - summary: `Accepted Read-Need ${accepted.needId || 'for Need-Fit search'}.`, + summary: `Accepted Read-Need ${accepted.needId || 'for Finding Fits'}.`, context: { source: 'terminal-staged-reading', needId: accepted.needId, @@ -549,7 +536,19 @@ export default function TerminalDepositReadWorkbench({ summary="Reading the live deposit-side source, read measurement, and asset-pack fit posture." explainer={TERMINAL_WORKSPACE_EXPLAINERS.depositReadChain} > -

Reading the live Bitcode workbench…

+

+ Reading the live Bitcode workbench. The enterprise Reading path runs + ReadNeedComprehensionSynthesis before ReadFindingFitsSynthesis, and it remains + visible even before a repository is selected. +

+
    + {TERMINAL_ENTERPRISE_READING_STEPS.map((stage) => ( +
  1. +

    {stage.label}

    +

    {stage.detail}

    +
  2. + ))} +
); } @@ -621,7 +620,7 @@ export default function TerminalDepositReadWorkbench({

staged reading

-

Read Request, Read-Need, Need-Fit, preview, settlement, unlock

+

Request Read, review Need, request Fit, review AssetPack, buy and settle

The live pipeline starts from an accepted Read-Need. Preview can expose measurements, roots, score, fee quote, and range posture, while source-bearing AssetPack material stays locked until settlement readback.

@@ -631,7 +630,7 @@ export default function TerminalDepositReadWorkbench({
-
+
{stageCards.map((stage) => { const active = stage.id === activeReadingStage; return ( @@ -720,7 +719,7 @@ export default function TerminalDepositReadWorkbench({ }} className="rounded-[1.2rem] border border-amber-300/30 bg-amber-300/10 px-4 py-3 text-sm font-medium text-amber-100 transition hover:border-amber-200/50 hover:bg-amber-300/15 disabled:cursor-not-allowed disabled:opacity-55" > - {harnessState === 'running' ? 'Running Need-Fit…' : 'Run Need-Fit pipeline'} + {harnessState === 'running' ? 'Running Finding Fits…' : 'Request Fit'}
@@ -764,7 +763,7 @@ export default function TerminalDepositReadWorkbench({

read state

Measured Read before fit result

- Recording a Read stores the measured demand frame. It does not mean Bitcode found a fit. Fit search must then return + Recording a Read stores the measured demand frame. It does not mean Bitcode found a fit. Finding Fits must then return worthy_fit, no_worthy_fit, or blocked_readiness before settlement or finality can proceed.

@@ -777,7 +776,7 @@ export default function TerminalDepositReadWorkbench({ {[ { id: 'draft', label: '1. Read framed', detail: 'Repository, branch, commit, and demand frame are visible.' }, { id: 'measured', label: '2. Read measured', detail: 'Read posture is persisted as ledger evidence.' }, - { id: 'admitted', label: '3. Fit admitted', detail: 'Measured Read may enter source-bound fit search.' }, + { id: 'admitted', label: '3. Fit admitted', detail: 'Measured Read may enter source-bound Finding Fits.' }, { id: 'fit-recorded', label: '4. Result recorded', detail: 'Fit result posture is reviewable before proof or settlement.' }, ].map((step) => { const active = step.id === readFitProgress; diff --git a/uapi/app/terminal/TerminalOperationalHealthPanel.tsx b/uapi/app/terminal/TerminalOperationalHealthPanel.tsx new file mode 100644 index 000000000..1f32bd0be --- /dev/null +++ b/uapi/app/terminal/TerminalOperationalHealthPanel.tsx @@ -0,0 +1,199 @@ +'use client'; + +import React from 'react'; +import { + buildTerminalOperationalHealthRead, + type TerminalOperationalHealthRead, + type TerminalOperationalHealthSeverity, + type TerminalOperationalReadinessState, +} from '@bitcode/btd/terminal-operational-health'; + +const DEFAULT_OPERATIONAL_HEALTH_READ = buildTerminalOperationalHealthRead(); + +const STATE_TONE: Record = { + ready: 'border-emerald-300/35 bg-emerald-400/10 text-emerald-100', + review: 'border-amber-300/35 bg-amber-400/10 text-amber-100', + blocked: 'border-rose-300/35 bg-rose-400/10 text-rose-100', + disabled: 'border-neutral-500/35 bg-neutral-500/10 text-neutral-200', + future: 'border-sky-300/30 bg-sky-400/10 text-sky-100', +}; + +const SEVERITY_TONE: Record = { + none: 'border-neutral-500/35 bg-neutral-500/10 text-neutral-200', + info: 'border-emerald-300/35 bg-emerald-400/10 text-emerald-100', + warning: 'border-amber-300/35 bg-amber-400/10 text-amber-100', + critical: 'border-rose-300/35 bg-rose-400/10 text-rose-100', +}; + +export default function TerminalOperationalHealthPanel({ + healthRead = DEFAULT_OPERATIONAL_HEALTH_READ, +}: { + healthRead?: TerminalOperationalHealthRead; +}) { + const activeProvider = healthRead.providers.find((provider) => provider.state === 'ready'); + const bitcoinSettlement = healthRead.settlementNetworks.find( + (network) => network.id === 'bitcoin-taproot-psbt', + ); + + return ( +
+
+
+

+ Operations readiness +

+

+ Terminal lanes, telemetry, upgrade, and testnet minting +

+
+
+ + +
+
+ +
+ {healthRead.lanes.map((lane) => ( +
+
+
+

{lane.lane}

+

{lane.label}

+
+ + {lane.state} + +
+
+
+
Network
+
{lane.bitcoinNetwork} / {lane.ledgerNetwork}
+
+
+
Value bearing
+
{lane.valueBearing ? 'yes' : 'no'}
+
+
+
Approval
+
{lane.approvalPosture}
+
+
+
+ ))} +
+ +
+
+ {[healthRead.broadcaster, healthRead.observer].map((subsystem) => ( +
+
+

{subsystem.label}

+ + {subsystem.severity} + +
+

{subsystem.summary}

+
+ ))} +
+ +
+
+

Upgrade and generated types

+ + {healthRead.upgrade.generatedTypeRefresh.state} + +
+
+ + + +
+
+
+ +
+
+

VCS scope

+
+ {healthRead.providers.map((provider) => ( + + ))} +
+
+ +
+

Settlement networks

+
+ {healthRead.settlementNetworks.map((network) => ( + + ))} +
+ {bitcoinSettlement ? ( +

{bitcoinSettlement.summary}

+ ) : null} +
+ +
+

Testnet mint readback

+
+ + + + + +
+
+
+
+ ); +} + +function OperationalPill({ + label, + value, + tone, +}: { + label: string; + value: string; + tone: string; +}) { + return ( +
+ {label} + {value} +
+ ); +} + +function OperationalDefinition({ label, value }: { label: string; value: string }) { + return ( +
+
{label}
+
{value}
+
+ ); +} + +function OperationalStatusRow({ + label, + state, +}: { + label: string; + state: TerminalOperationalReadinessState; +}) { + return ( +
+ {label} + + {state} + +
+ ); +} diff --git a/uapi/app/terminal/TerminalPageClient.tsx b/uapi/app/terminal/TerminalPageClient.tsx index 383052aa4..3b1f210a9 100644 --- a/uapi/app/terminal/TerminalPageClient.tsx +++ b/uapi/app/terminal/TerminalPageClient.tsx @@ -29,6 +29,7 @@ import TerminalSectionAtlas from './TerminalSectionAtlas'; import TerminalSurfaceSection from './TerminalSurfaceSection'; import TerminalSupplySelectionPanel from './TerminalSupplySelectionPanel'; import TerminalMvpMap from './TerminalMvpMap'; +import TerminalOperationalHealthPanel from './TerminalOperationalHealthPanel'; import TerminalTransactionWorkspace from './TerminalTransactionWorkspace'; import TerminalWorkspaceRail from './TerminalWorkspaceRail'; import { @@ -480,6 +481,8 @@ export default function TerminalPageClient() { + +
- {isReviewing ? 'Reviewing Read…' : 'Accept Read for fit search'} + {isReviewing ? 'Reviewing Read…' : 'Accept Read for Finding Fits'}
diff --git a/uapi/app/terminal/terminal-activity-history.ts b/uapi/app/terminal/terminal-activity-history.ts index 5e1f7f9bc..b1a2b2fa2 100644 --- a/uapi/app/terminal/terminal-activity-history.ts +++ b/uapi/app/terminal/terminal-activity-history.ts @@ -407,18 +407,18 @@ export function buildTerminalReadAdmissionDraft( fitSearchAdmission: { admitted: true, admissionReason: - 'Measured Read is admitted for generic source-bound fit search against the selected deposited repository revision.', - admittedStages: ['candidate-recall', 'fit-quality-evaluation', 'asset-pack-result-review'], + 'Measured Read is admitted for source-bound Finding Fits against the selected deposited repository revision.', + admittedStages: ['finding-fits-discovery', 'fit-quality-evaluation', 'asset-pack-result-review'], blockedStages: ['settlement', 'finality', 'minting'], }, - nextProtocolAction: 'Run fit search and return worthy_fit, no_worthy_fit, or blocked_readiness evidence.', + nextProtocolAction: 'Run Finding Fits and return worthy_fit, no_worthy_fit, or blocked_readiness evidence.', }; return { type: 'agentic-execution:read-measurement', detailSection: 'activity', sourceRevision: workbench.sourceRevision, - summary: `Accepted measured Read for fit search for ${workbench.scenarioLabel}.`, + summary: `Accepted measured Read for Finding Fits for ${workbench.scenarioLabel}.`, output: { readMeasurement, readReview, diff --git a/uapi/app/terminal/terminal-closure-state.ts b/uapi/app/terminal/terminal-closure-state.ts index de1429a96..aa60d22e2 100644 --- a/uapi/app/terminal/terminal-closure-state.ts +++ b/uapi/app/terminal/terminal-closure-state.ts @@ -198,15 +198,15 @@ export function normalizeTerminalClosureState(snapshot: ShellSnapshot): Terminal canonLabel: stringValue(snapshot.canonLabel, 'Bitcode active posture'), readReview: { id: 'read-review', - label: stringValue(readReview.label, 'Read review before fit search'), + label: stringValue(readReview.label, 'Read review before Finding Fits'), summary: stringValue( readReview.admissionReason || readReview.measuredTask, - 'Measured Read must be accepted, rejected, or sent back for remeasurement before Bitcode can search for fitting AssetPacks.', + 'Measured Read must be accepted, rejected, or sent back for remeasurement before Bitcode can run Finding Fits for AssetPacks.', ), metrics: [ { label: 'Review action', value: stringValue(readReview.reviewAction) }, { label: 'Review status', value: stringValue(readReview.reviewStatus) }, - { label: 'Fit search admitted', value: readReview.fitSearchAdmitted === true ? 'yes' : 'no' }, + { label: 'Finding Fits admitted', value: readReview.fitSearchAdmitted === true ? 'yes' : 'no' }, { label: 'Protocol focus', value: stringValue(readReview.protocolFocus, 'source-to-shares') }, ], rows: [ diff --git a/uapi/app/terminal/terminal-deposit-read-workbench.ts b/uapi/app/terminal/terminal-deposit-read-workbench.ts index b6e327c9c..11174fdcc 100644 --- a/uapi/app/terminal/terminal-deposit-read-workbench.ts +++ b/uapi/app/terminal/terminal-deposit-read-workbench.ts @@ -25,6 +25,45 @@ export type TerminalDepositedSourceRevision = TerminalSourceRevision & { hasAssetMeasurementEvidence?: boolean | null; }; +export type TerminalEnterpriseReadingStepId = + | 'request-read' + | 'review-synthesized-need' + | 'request-fit' + | 'review-synthesized-asset-pack' + | 'buy-asset-pack-settle'; + +export const TERMINAL_ENTERPRISE_READING_STEPS: Array<{ + id: TerminalEnterpriseReadingStepId; + label: string; + detail: string; +}> = [ + { + id: 'request-read', + label: '1. Request Read', + detail: 'Repository, branch, commit, and reader request are framed.', + }, + { + id: 'review-synthesized-need', + label: '2. Review synthesized Need', + detail: 'Bitcode shows requirements, measurements, constraints, proof expectations, and feedback history.', + }, + { + id: 'request-fit', + label: '3. Request Fit', + detail: 'Only an accepted Need can search deposited source and synthesize fit evidence.', + }, + { + id: 'review-synthesized-asset-pack', + label: '4. Review synthesized AssetPack', + detail: 'Source-safe preview shows measurements, roots, score, fee quote, disclosure policy, and range posture.', + }, + { + id: 'buy-asset-pack-settle', + label: '5. Buy AssetPack, settle', + detail: 'Reader BTC payment, BTD range/license readback, journal, ledger, and pull-request delivery close the purchase.', + }, +]; + type ShellSnapshot = { canonLabel?: string | null; sourceRevision?: TerminalSourceRevision | null; @@ -130,7 +169,7 @@ export function buildLiveTerminalDepositReadWorkbenchSnapshot( `Read the deposited source revision ${selectedRevisionLabel} for a non-mock Terminal path from wallet and GitHub readiness through Deposit, Read/Fit, AssetPack evidence, proof/finality readback, and Supabase/ledger reconciliation.`; const closureCriteria = [ 'Deposit evidence is bound to repository, branch, commit, and signer.', - 'Read measurement is accepted before fit search or blocks with a precise reason.', + 'Read measurement is accepted before Finding Fits discovery or blocks with a precise reason.', 'Fit evidence references the deposited repository revision and candidate AssetPack.', 'AssetPack, proof, finality, and reconciliation posture are visible or explicitly blocked.', 'No mock, frontier, or protocol-demo repository is treated as live staging source.', @@ -214,7 +253,7 @@ export function buildLiveTerminalDepositReadWorkbenchSnapshot( failureModes: [ 'mock repository leakage', 'missing repository revision evidence', - 'read review not admitted before fit search', + 'read review not admitted before Finding Fits discovery', 'AssetPack fit without proof or finality posture', 'ledger/database readback drift', ], diff --git a/uapi/app/terminal/terminal-pipeline-harness-client.ts b/uapi/app/terminal/terminal-pipeline-harness-client.ts index a76af067b..4bb7e5b5f 100644 --- a/uapi/app/terminal/terminal-pipeline-harness-client.ts +++ b/uapi/app/terminal/terminal-pipeline-harness-client.ts @@ -307,7 +307,13 @@ function classifyHarnessLogType(event: TerminalFitPipelineHarnessEvent): string namespace === 'llm' || key.includes('parsedoutput') || typeof telemetryEvent?.inputMessageCount === 'number' || - typeof telemetryEvent?.outputContentLength === 'number' + typeof telemetryEvent?.outputContentLength === 'number' || + telemetryEvent?.promptTemplatePresent === true || + telemetryEvent?.interpolatedPromptPresent === true || + telemetryEvent?.reasoningPresent === true || + telemetryEvent?.judgmentPresent === true || + telemetryEvent?.rawModelResponsePresent === true || + telemetryEvent?.parsedTypedOutputPresent === true ) { return 'generation'; } @@ -439,6 +445,7 @@ export function buildTerminalFitPipelineHarnessStreamSnapshot( harnessEvent: event.event, harnessPayload: event.data, telemetryEvent, + inferenceAudit: telemetryEvent?.inferenceAudit || null, }, }, }; @@ -495,6 +502,12 @@ function summarizeTelemetryArtifactEvent(data: Record): string ? `${telemetryEvent.outputContentLength} output chars` : null; const parsedOutput = telemetryEvent.parsedOutputPresent === true ? 'parsed output present' : null; + const promptTemplate = telemetryEvent.promptTemplatePresent === true ? 'prompt template present' : null; + const interpolatedPrompt = telemetryEvent.interpolatedPromptPresent === true ? 'interpolated prompt present' : null; + const reasoning = telemetryEvent.reasoningPresent === true ? 'reasoning present' : null; + const judgment = telemetryEvent.judgmentPresent === true ? 'judgment present' : null; + const rawResponse = telemetryEvent.rawModelResponsePresent === true ? 'raw response present' : null; + const parsedTypedOutput = telemetryEvent.parsedTypedOutputPresent === true ? 'parsed typed output present' : null; const toolDetails = [ telemetryEvent.toolInputPresent === true ? 'input' : null, telemetryEvent.toolOutputPresent === true ? 'output' : null, @@ -511,7 +524,13 @@ function summarizeTelemetryArtifactEvent(data: Record): string inspectableKeys.length ? `inspectable ${inspectableKeys.join(', ')}` : null, inputMessageCount, outputContentLength, + promptTemplate, + interpolatedPrompt, + reasoning, + judgment, + rawResponse, parsedOutput, + parsedTypedOutput, ].filter(Boolean).join('; ') + '.'; } diff --git a/uapi/app/terminal/terminal-read-scenarios.ts b/uapi/app/terminal/terminal-read-scenarios.ts index 0fcd40e96..82b5147a4 100644 --- a/uapi/app/terminal/terminal-read-scenarios.ts +++ b/uapi/app/terminal/terminal-read-scenarios.ts @@ -130,7 +130,7 @@ export function normalizeTerminalReadFittingReview(payload: unknown): TerminalRe status: stringValue(review.status || reviewableRead.status, 'ready-for-review'), action: stringValue(review.action || recordValue(root.reviewDecision).action, 'pending operator review'), fitSearchAdmitted: fitSearchAdmission.admitted === true, - admissionReason: stringValue(fitSearchAdmission.admissionReason, 'Read must be accepted before fit search begins.'), + admissionReason: stringValue(fitSearchAdmission.admissionReason, 'Read must be accepted before Finding Fits begins.'), blockedStages: stringList(fitSearchAdmission.blockedStages || candidateFitRequirements.blockedStages), admittedStages: stringList(fitSearchAdmission.admittedStages || candidateFitRequirements.admittedStages), allowedActions: stringList(review.allowedActions || root.allowedActions || reviewableRead.allowedActions), diff --git a/uapi/app/terminal/terminal-transaction-detail-snapshot.ts b/uapi/app/terminal/terminal-transaction-detail-snapshot.ts index 95ef140c3..bedcc165c 100644 --- a/uapi/app/terminal/terminal-transaction-detail-snapshot.ts +++ b/uapi/app/terminal/terminal-transaction-detail-snapshot.ts @@ -540,7 +540,7 @@ function coerceClosureState(value: unknown): TerminalClosureState | null { const verification = coerceClosurePanel(value.verification); const readReview = coerceClosurePanel(value.readReview) || { id: 'read-review' as const, - label: 'Read review before fit search', + label: 'Read review before Finding Fits', summary: 'Read review was not persisted on this older closure snapshot.', metrics: [], rows: [], diff --git a/uapi/jest.config.cjs b/uapi/jest.config.cjs index 24947e813..9a1282a71 100644 --- a/uapi/jest.config.cjs +++ b/uapi/jest.config.cjs @@ -40,6 +40,7 @@ module.exports = { '^@bitcode/google-analytics$': '/../packages/google-analytics/src/ga.ts', '^@bitcode/errors$': '/../packages/errors/src/errors.ts', '^@bitcode/artifacts$': '/../packages/artifacts/src/artifacts.ts', + '^@bitcode/btd/terminal-operational-health$': '/../packages/btd/src/terminal-operational-health.ts', '^@bitcode/vcs-tools$': '/../packages/generic-tools/vcs/src/index.ts', '^@bitcode/protocol$': '/../packages/protocol/src/index.js', '^@bitcode/protocol/src/(.+)$': '/../packages/protocol/src/$1', @@ -48,6 +49,7 @@ module.exports = { '^@bitcode/generic-tools-mcps-(.+)$': '/../packages/generic-tools/mcps-tools/$1/src/index.ts', '^@bitcode/generic-tools-(.+)$': '/../packages/generic-tools/$1/src/index.ts', '^@bitcode/pipeline-asset-pack/read-need$': '/../packages/pipelines/asset-pack/src/read-need.ts', + '^@bitcode/pipeline-asset-pack/reading-pipeline-contract$': '/../packages/pipelines/asset-pack/src/reading-pipeline-contract.ts', '^@bitcode/pipeline-asset-pack/src/(.+)$': '/../packages/pipelines/asset-pack/src/$1', '^@bitcode/([^/]+)/src/(.+)$': '/../packages/$1/src/$2', '^@bitcode/supabase/ssr/server$': '/tests/mocks/supabaseServerClient.ts', @@ -129,6 +131,7 @@ module.exports = { '/tests/terminalCommandState.test.ts', '/tests/terminalExperienceArchitecture.test.ts', '/tests/terminalCommercialLaunchReadiness.test.ts', + '/tests/terminalOperationalHealthPanel.test.tsx', '/tests/terminalShellBridge.test.tsx', '/tests/terminalTransactionSource.test.ts', '/tests/terminalRepositoryContext.test.ts', diff --git a/uapi/lib/bitcode-app-context.ts b/uapi/lib/bitcode-app-context.ts index 87ee605ed..83deb6f1e 100644 --- a/uapi/lib/bitcode-app-context.ts +++ b/uapi/lib/bitcode-app-context.ts @@ -28,7 +28,7 @@ export function toBitcodeErrorResponse(error: unknown) { if (!resolvedError.statusCode && /No candidates survived into the asset pack/i.test(resolvedError.message || '')) { resolvedError.statusCode = 409; } - if (!resolvedError.statusCode && /fit search cannot proceed/i.test(resolvedError.message || '')) { + if (!resolvedError.statusCode && /(Finding Fits|fit search) cannot proceed/i.test(resolvedError.message || '')) { resolvedError.statusCode = 409; } return NextResponse.json( diff --git a/uapi/next.config.mjs b/uapi/next.config.mjs index 9f85312c2..5a02e3262 100644 --- a/uapi/next.config.mjs +++ b/uapi/next.config.mjs @@ -108,6 +108,7 @@ let nextConfig = { '@bitcode/vcs-tools', // Core shared libs commonly imported in app/server code '@bitcode/protocol', + '@bitcode/btd', '@bitcode/models', '@bitcode/files', '@bitcode/logger', diff --git a/uapi/package.json b/uapi/package.json index 169bf4856..d7d47e002 100644 --- a/uapi/package.json +++ b/uapi/package.json @@ -39,10 +39,12 @@ "@ai-sdk/google": "1.0.4", "@ai-sdk/openai": "1.0.6", "@aws-sdk/client-s3": "^3.408.0", + "@bitcode/btd": "workspace:*", "@bitcode/email": "workspace:*", "@bitcode/errors": "workspace:*", "@bitcode/google-analytics": "workspace:*", "@bitcode/observability": "workspace:*", + "@bitcode/pipeline-asset-pack": "workspace:*", "@bitcode/pipeline-hosts": "workspace:*", "@bitcode/prompts": "workspace:*", "@bitcode/protocol": "workspace:*", diff --git a/uapi/tests/api/executionsHistoryWriteReadParity.test.ts b/uapi/tests/api/executionsHistoryWriteReadParity.test.ts index e3fd53ebf..13008cb7d 100644 --- a/uapi/tests/api/executionsHistoryWriteReadParity.test.ts +++ b/uapi/tests/api/executionsHistoryWriteReadParity.test.ts @@ -142,9 +142,9 @@ describe('Bitcode execution-history write/read parity', () => { canonLabel: 'Bitcode active posture', readReview: { id: 'read-review', - label: 'Read review before fit search', - summary: 'Measured Read accepted for source-to-shares fit search.', - metrics: [{ label: 'Fit search admitted', value: 'yes' }], + label: 'Read review before Finding Fits', + summary: 'Measured Read accepted for source-to-shares Finding Fits.', + metrics: [{ label: 'Finding Fits admitted', value: 'yes' }], rows: [{ label: 'Review stage', value: 'post-measurement-pre-fit' }], chips: ['source-to-shares'], }, @@ -368,7 +368,7 @@ describe('Bitcode execution-history write/read parity', () => { asset_pack_completion: expect.objectContaining({ closurePanels: expect.objectContaining({ readReview: expect.objectContaining({ - label: 'Read review before fit search', + label: 'Read review before Finding Fits', }), }), }), diff --git a/uapi/tests/api/pipelineHarnessRoute.test.ts b/uapi/tests/api/pipelineHarnessRoute.test.ts index 14dcf70f3..6d3f1dcf7 100644 --- a/uapi/tests/api/pipelineHarnessRoute.test.ts +++ b/uapi/tests/api/pipelineHarnessRoute.test.ts @@ -188,7 +188,7 @@ function acceptedReadNeed() { status: 'accepted', acceptedAt: '2026-05-18T00:00:00.000Z', acceptanceRoot: 'sha256:acceptance-root', - nextStage: 'need_fit_search', + nextStage: 'finding_fits', }, }; } diff --git a/uapi/tests/api/readReviewProtocolParity.test.ts b/uapi/tests/api/readReviewProtocolParity.test.ts index 85cec21a9..35d2ea8f9 100644 --- a/uapi/tests/api/readReviewProtocolParity.test.ts +++ b/uapi/tests/api/readReviewProtocolParity.test.ts @@ -27,7 +27,7 @@ function mapBitcodeError(error: unknown) { error instanceof Error ? (error as Error & { statusCode?: number }) : new Error('Unknown error.'); const message = resolvedError.message || 'Unknown error.'; const status = - resolvedError.statusCode || (/fit search cannot proceed/i.test(message) ? 409 : 500); + resolvedError.statusCode || (/Finding Fits cannot proceed/i.test(message) ? 409 : 500); return new Response(JSON.stringify({ error: message }), { status, @@ -232,7 +232,7 @@ describe('V26 Read-review SPEC-IMPL parity across protocol and commercial API', body: JSON.stringify({ scenarioId: 'auth-issuer-rollback', readReviewAction: 'remeasure-with-feedback', - readReviewFeedback: ['Clarify rollback ordering before fit search.'], + readReviewFeedback: ['Clarify rollback ordering before Finding Fits.'], }), }), ); @@ -252,7 +252,7 @@ describe('V26 Read-review SPEC-IMPL parity across protocol and commercial API', expect(reviewPayload.nextProtocolAction).toContain('/api/read-review'); }); - it('blocks commercial branch materialization when the protocol Read review rejects fit search', async () => { + it('blocks commercial branch materialization when the protocol Read review rejects Finding Fits', async () => { const protocol = (await import( '@bitcode/protocol/src/bitcode-demo.js' )) as BitcodeProtocolRuntime; @@ -283,7 +283,7 @@ describe('V26 Read-review SPEC-IMPL parity across protocol and commercial API', const payload = await response.json(); expect(response.status).toBe(409); - expect(payload.error).toMatch(/fit search cannot proceed/i); + expect(payload.error).toMatch(/Finding Fits cannot proceed/i); }); it('carries accepted source-to-shares settlement artifacts through the commercial branch route', async () => { diff --git a/uapi/tests/api/readReviewRoute.test.ts b/uapi/tests/api/readReviewRoute.test.ts index 20e9a4862..497811fac 100644 --- a/uapi/tests/api/readReviewRoute.test.ts +++ b/uapi/tests/api/readReviewRoute.test.ts @@ -75,7 +75,7 @@ describe('/api/read-review', () => { jest.clearAllMocks(); }); - it('presents a reviewable Read before fit search', async () => { + it('presents a reviewable Read before Finding Fits', async () => { const response = await GET( new Request('http://localhost/api/read-review?scenarioId=scenario-auth'), ); @@ -95,7 +95,7 @@ describe('/api/read-review', () => { }); }); - it('records explicit operator review decisions before fit search admission', async () => { + it('records explicit operator review decisions before Finding Fits admission', async () => { const response = await POST( new Request('http://localhost/api/read-review', { method: 'POST', @@ -124,7 +124,7 @@ describe('/api/read-review', () => { }); }); - it('synthesizes a reviewable Read-Need before Need-Fit search', async () => { + it('synthesizes a reviewable Read-Need before Finding Fits', async () => { const response = await POST( new Request('http://localhost/api/read-review', { method: 'POST', @@ -146,7 +146,8 @@ describe('/api/read-review', () => { expect(response.status).toBe(200); expect(mockReviewNeed).not.toHaveBeenCalled(); - expect(payload.stage).toBe('read_need_review'); + expect(payload.pipelineName).toBe('ReadNeedComprehensionSynthesis'); + expect(payload.stage).toBe('review_synthesized_need'); expect(payload.readNeed).toMatchObject({ schema: 'bitcode.read.need', reviewState: 'needs_acceptance', @@ -163,12 +164,21 @@ describe('/api/read-review', () => { }); expect(payload.telemetry).toMatchObject({ schema: 'bitcode.read-need.synthesis-telemetry', + pipelineName: 'ReadNeedComprehensionSynthesis', + phaseId: 'ReadNeedComprehensionSynthesis.comprehend', + agentId: 'ReadNeedComprehensionSynthesis.comprehend.need-synthesizer', + ptrrStepId: 'ReadNeedComprehensionSynthesis.comprehend.need-synthesizer.try', + returnType: 'ReadNeed', measurementRoot: payload.readNeed.measurementRoot, reviewState: 'needs_acceptance', }); + expect(payload.telemetry.promptTemplate.templateId).toBe( + 'ReadNeedComprehensionSynthesis.prompt.need-synthesis', + ); + expect(payload.telemetry.parsedTypedOutput).toMatchObject({ schema: 'bitcode.read.need' }); }); - it('accepts a synthesized Read-Need as the only Need-Fit input', async () => { + it('accepts a synthesized Read-Need as the only Finding Fits input', async () => { const synthesisResponse = await POST( new Request('http://localhost/api/read-review', { method: 'POST', @@ -196,21 +206,26 @@ describe('/api/read-review', () => { const payload = await acceptResponse.json(); expect(acceptResponse.status).toBe(200); - expect(payload.stage).toBe('need_fit_ready'); + expect(payload.pipelineName).toBe('ReadNeedComprehensionSynthesis'); + expect(payload.nextPipelineName).toBe('ReadFindingFitsSynthesis'); + expect(payload.stage).toBe('request_fit_ready'); expect(payload.acceptedReadNeed).toMatchObject({ schema: 'bitcode.read.need', reviewState: 'accepted', needId: synthesis.readNeed.needId, measurementRoot: synthesis.readNeed.measurementRoot, }); - expect(payload.fitSearchAdmission).toMatchObject({ + expect(payload.findingFitsAdmission).toMatchObject({ admitted: true, blockers: [], }); expect(payload.telemetry).toMatchObject({ schema: 'bitcode.read-need.acceptance-telemetry', + pipelineName: 'ReadNeedComprehensionSynthesis', + nextPipelineName: 'ReadFindingFitsSynthesis', needId: synthesis.readNeed.needId, - nextStage: 'need_fit_search', + nextStage: 'finding_fits', + returnType: 'AcceptedReadNeed', }); }); }); diff --git a/uapi/tests/terminalActivityHistory.test.ts b/uapi/tests/terminalActivityHistory.test.ts index b85a01eb8..49445431a 100644 --- a/uapi/tests/terminalActivityHistory.test.ts +++ b/uapi/tests/terminalActivityHistory.test.ts @@ -47,9 +47,9 @@ describe('terminal-activity-history', () => { canonLabel: 'Bitcode active posture', readReview: { id: 'read-review', - label: 'Read review before fit search', - summary: 'Measured Read accepted for source-to-shares fit search.', - metrics: [{ label: 'Fit search admitted', value: 'yes' }], + label: 'Read review before Finding Fits', + summary: 'Measured Read accepted for source-to-shares Finding Fits.', + metrics: [{ label: 'Finding Fits admitted', value: 'yes' }], rows: [{ label: 'Review stage', value: 'post-measurement-pre-fit' }], chips: ['source-to-shares'], }, @@ -233,7 +233,7 @@ describe('terminal-activity-history', () => { ], closureCriteria: [ 'deposit evidence is bound to repo, branch, and commit', - 'read measurement is accepted before fit search', + 'read measurement is accepted before Finding Fits', 'fit evidence references the deposited AssetPack candidate', 'no frontier or mock repository is present', 'settlement and finality are explicit or blocked with a reason', @@ -398,7 +398,7 @@ describe('terminal-activity-history', () => { canonLabel: 'Bitcode active posture', readReview: { id: 'read-review', - label: 'Read review before fit search', + label: 'Read review before Finding Fits', }, verification: { id: 'verification', diff --git a/uapi/tests/terminalClosureState.test.ts b/uapi/tests/terminalClosureState.test.ts index 90c677a45..c357d174e 100644 --- a/uapi/tests/terminalClosureState.test.ts +++ b/uapi/tests/terminalClosureState.test.ts @@ -6,7 +6,7 @@ describe('normalizeTerminalClosureState', () => { canonLabel: 'production workspace posture', closureSurface: { readReview: { - label: 'Read review before fit search', + label: 'Read review before Finding Fits', readId: 'read-auth-rollback', protocolFocus: 'source-to-shares', reviewStage: 'post-measurement-pre-fit', @@ -15,7 +15,7 @@ describe('normalizeTerminalClosureState', () => { reviewer: 'bitcode-system:read-review', decisionMode: 'deterministic-local-review', fitSearchAdmitted: true, - admissionReason: 'Measured Read accepted for source-to-shares fit search.', + admissionReason: 'Measured Read accepted for source-to-shares Finding Fits.', allowedActions: ['accept', 'reject', 'remeasure-with-feedback'], measuredTask: 'Recover an auth rollback.', measurementHash: 'sha256:measurement', @@ -109,7 +109,7 @@ describe('normalizeTerminalClosureState', () => { }); expect(closure?.verification.metrics.find((entry) => entry.label === 'Candidates')?.value).toBe('5'); - expect(closure?.readReview.metrics.find((entry) => entry.label === 'Fit search admitted')?.value).toBe('yes'); + expect(closure?.readReview.metrics.find((entry) => entry.label === 'Finding Fits admitted')?.value).toBe('yes'); expect(closure?.readReview.rows.find((entry) => entry.label === 'Allowed actions')?.value).toContain( 'remeasure-with-feedback', ); diff --git a/uapi/tests/terminalDepositReadWorkbench.test.ts b/uapi/tests/terminalDepositReadWorkbench.test.ts index a52727b1e..c0d30c3c3 100644 --- a/uapi/tests/terminalDepositReadWorkbench.test.ts +++ b/uapi/tests/terminalDepositReadWorkbench.test.ts @@ -1,10 +1,29 @@ import { buildLiveTerminalDepositReadWorkbenchSnapshot, normalizeTerminalDepositReadWorkbench, + TERMINAL_ENTERPRISE_READING_STEPS, } from '@/app/terminal/terminal-deposit-read-workbench'; import type { TerminalRepositoryContextState } from '@/app/terminal/terminal-repository-context'; describe('normalizeTerminalDepositReadWorkbench', () => { + it('locks the enterprise Reading UX to five reviewable steps', () => { + expect(TERMINAL_ENTERPRISE_READING_STEPS.map((step) => step.id)).toEqual([ + 'request-read', + 'review-synthesized-need', + 'request-fit', + 'review-synthesized-asset-pack', + 'buy-asset-pack-settle', + ]); + expect(TERMINAL_ENTERPRISE_READING_STEPS.map((step) => step.label)).toEqual([ + '1. Request Read', + '2. Review synthesized Need', + '3. Request Fit', + '4. Review synthesized AssetPack', + '5. Buy AssetPack, settle', + ]); + expect(TERMINAL_ENTERPRISE_READING_STEPS).toHaveLength(5); + }); + it('builds deposit, read, and fit sections from the shell snapshot', () => { const workbench = normalizeTerminalDepositReadWorkbench( { diff --git a/uapi/tests/terminalOperationalHealthPanel.test.tsx b/uapi/tests/terminalOperationalHealthPanel.test.tsx new file mode 100644 index 000000000..286ec9a9c --- /dev/null +++ b/uapi/tests/terminalOperationalHealthPanel.test.tsx @@ -0,0 +1,44 @@ +import '@testing-library/jest-dom'; +import React from 'react'; +import { render, screen, within } from '@testing-library/react'; + +import TerminalOperationalHealthPanel from '@/app/terminal/TerminalOperationalHealthPanel'; +import { buildTerminalOperationalHealthRead } from '@bitcode/btd/terminal-operational-health'; + +describe('TerminalOperationalHealthPanel', () => { + it('renders lane, telemetry, upgrade, provider, settlement, and minting readiness', () => { + render(); + + expect(screen.getByRole('heading', { name: /Terminal lanes, telemetry, upgrade, and testnet minting/i })).toBeInTheDocument(); + expect(screen.getByText('mainnet-value-bearing')).toBeInTheDocument(); + expect(screen.getByText('Blocked until an operational approval root is present.')).toBeInTheDocument(); + expect(screen.getByText('BTC broadcaster')).toBeInTheDocument(); + expect(screen.getByText('Ledger observer')).toBeInTheDocument(); + expect(screen.getByText('Migration root')).toBeInTheDocument(); + expect(screen.getByText('terminal-migration-root')).toBeInTheDocument(); + expect(screen.getByText('bitcoin-taproot-psbt')).toBeInTheDocument(); + expect(screen.getByText('binance-web3-wallet')).toBeInTheDocument(); + expect(screen.getByText('terminal-testnet-asset-pack')).toBeInTheDocument(); + expect(screen.getByText('in sync')).toBeInTheDocument(); + }); + + it('renders a ready mainnet value-bearing lane when the package model admits approval', () => { + const healthRead = buildTerminalOperationalHealthRead({ + issuedAt: 'terminal-operational-health-panel-test', + operationalApprovalRoots: { + 'mainnet-value-bearing': 'approval-root', + }, + generatedTypeRefreshState: 'current', + }); + + render(); + + const mainnetValueLane = screen.getByText('mainnet-value-bearing').closest('article'); + expect(mainnetValueLane).not.toBeNull(); + expect(within(mainnetValueLane as HTMLElement).getByText('ready')).toBeInTheDocument(); + expect( + within(mainnetValueLane as HTMLElement).getByText('Operational approval root present for value-bearing mainnet.'), + ).toBeInTheDocument(); + expect(screen.getByText('current')).toBeInTheDocument(); + }); +}); diff --git a/uapi/tests/terminalPipelineHarnessClient.test.ts b/uapi/tests/terminalPipelineHarnessClient.test.ts index 8b082f6be..51f6fa8ef 100644 --- a/uapi/tests/terminalPipelineHarnessClient.test.ts +++ b/uapi/tests/terminalPipelineHarnessClient.test.ts @@ -119,7 +119,7 @@ describe('terminal pipeline harness client', () => { expect(state.missing).toContain('accepted Read-Need'); }); - it('requires accepted Read-Need review before live Need-Fit execution', () => { + it('requires accepted Read-Need review before live Finding Fits run', () => { const state = buildTerminalFitPipelineHarnessRequest({ workbench, repositoryContext, @@ -280,10 +280,16 @@ describe('terminal pipeline harness client', () => { stage: 'asset-pack-synthesis', namespace: 'llm', key: 'parsedOutput', - executionPath: ['asset_pack', 'synthesis', 'thriceified-generation'], + executionPath: ['asset_pack', 'synthesis', 'thricified-generation'], runId: '2bdcd936-a686-4a10-92e2-9c64cbef4f0e', dataKeys: ['parsed'], parsedOutputPresent: true, + promptTemplatePresent: true, + interpolatedPromptPresent: true, + reasoningPresent: true, + judgmentPresent: true, + rawModelResponsePresent: true, + parsedTypedOutputPresent: true, inspectable: { keys: ['resultState', 'assetPack'], }, @@ -294,10 +300,16 @@ describe('terminal pipeline harness client', () => { expect(summary).toContain('Telemetry line 8'); expect(summary).toContain('asset-pack-synthesis store'); expect(summary).toContain('llm.parsedOutput'); - expect(summary).toContain('path asset_pack > synthesis > thriceified-generation'); + expect(summary).toContain('path asset_pack > synthesis > thricified-generation'); expect(summary).toContain('run 2bdcd936-a68...'); expect(summary).toContain('inspectable resultState, assetPack'); + expect(summary).toContain('prompt template present'); + expect(summary).toContain('interpolated prompt present'); + expect(summary).toContain('reasoning present'); + expect(summary).toContain('judgment present'); + expect(summary).toContain('raw response present'); expect(summary).toContain('parsed output present'); + expect(summary).toContain('parsed typed output present'); }); it('summarizes live tool telemetry with tool name, result state, and input/output posture', () => { @@ -356,7 +368,17 @@ describe('terminal pipeline harness client', () => { }, inputMessageCount: 2, outputContentLength: 1200, + promptTemplatePresent: true, + interpolatedPromptPresent: true, + rawModelResponsePresent: true, parsedOutputPresent: true, + parsedTypedOutputPresent: true, + inferenceAudit: { + promptTemplate: { type: 'object', keys: ['templateId'] }, + interpolatedPrompt: { type: 'object', keys: ['messages'] }, + rawModelResponse: { type: 'string', length: 1200 }, + parsedTypedOutput: { type: 'object', keys: ['assetPack'] }, + }, }, }, }, @@ -412,6 +434,12 @@ describe('terminal pipeline harness client', () => { }, }, }); + expect((snapshot.outputDetails[telemetryLine as string] as any).status.metadata.inferenceAudit).toMatchObject({ + promptTemplate: { type: 'object', keys: ['templateId'] }, + interpolatedPrompt: { type: 'object', keys: ['messages'] }, + rawModelResponse: { type: 'string', length: 1200 }, + parsedTypedOutput: { type: 'object', keys: ['assetPack'] }, + }); const toolLine = snapshot.output .split('\n') .find((line) => line.includes('Telemetry line 10')); diff --git a/uapi/tests/terminalReadScenarios.test.ts b/uapi/tests/terminalReadScenarios.test.ts index ccdd70125..88dc97386 100644 --- a/uapi/tests/terminalReadScenarios.test.ts +++ b/uapi/tests/terminalReadScenarios.test.ts @@ -66,7 +66,7 @@ describe('normalizeTerminalReadScenarios', () => { requiredBefore: 'find-fitting-settlement', fitSearchAdmission: { admitted: false, - admissionReason: 'Read must be accepted before fit search begins.', + admissionReason: 'Read must be accepted before Finding Fits begins.', blockedStages: ['candidate-recall', 'find-fitting-settlement'], }, settlementReview: { diff --git a/uapi/tests/terminalSectionAtlas.test.ts b/uapi/tests/terminalSectionAtlas.test.ts index 18c0f02da..306bb47bc 100644 --- a/uapi/tests/terminalSectionAtlas.test.ts +++ b/uapi/tests/terminalSectionAtlas.test.ts @@ -45,13 +45,13 @@ describe('normalizeTerminalSectionAtlas', () => { }, closureSurface: { readReview: { - label: 'Read review before fit search', + label: 'Read review before Finding Fits', reviewAction: 'accept', reviewStatus: 'accepted', protocolFocus: 'source-to-shares', fitSearchAdmitted: true, allowedActions: ['accept', 'reject', 'remeasure-with-feedback'], - admissionReason: 'Measured Read accepted for source-to-shares fit search.', + admissionReason: 'Measured Read accepted for source-to-shares Finding Fits.', }, verification: { label: 'Verification + ranked candidates', @@ -100,8 +100,8 @@ describe('normalizeTerminalSectionAtlas', () => { expect(atlas[0]?.label).toBe('Operating picture'); expect(atlas[0]?.subheads).toContain('Repo supply'); expect(atlas[1]?.badge).toBe('Targeted deposit'); - expect(atlas[2]?.label).toBe('Read review before fit search'); - expect(atlas[2]?.subheads).toContain('Fit search admitted'); + expect(atlas[2]?.label).toBe('Read review before Finding Fits'); + expect(atlas[2]?.subheads).toContain('Finding Fits admitted'); expect(atlas[4]?.label).toBe('Verification + ranked candidates'); expect(atlas[4]?.subheads).toContain('Candidates'); expect(atlas[6]?.badge).toBe('selection-materialization'); diff --git a/uapi/tests/terminalTransactionDetail.test.ts b/uapi/tests/terminalTransactionDetail.test.ts index acc477669..14547577f 100644 --- a/uapi/tests/terminalTransactionDetail.test.ts +++ b/uapi/tests/terminalTransactionDetail.test.ts @@ -209,11 +209,11 @@ const closureState: TerminalClosureState = { canonLabel: 'production workspace posture', readReview: { id: 'read-review', - label: 'Read review before fit search', - summary: 'Measured Read accepted for source-to-shares fit search.', + label: 'Read review before Finding Fits', + summary: 'Measured Read accepted for source-to-shares Finding Fits.', metrics: [ { label: 'Review action', value: 'accept' }, - { label: 'Fit search admitted', value: 'yes' }, + { label: 'Finding Fits admitted', value: 'yes' }, ], rows: [{ label: 'Read', value: 'read-auth-rollback' }], chips: ['post-measurement-pre-fit', 'source-to-shares'], diff --git a/uapi/tests/terminalTransactionDetailSnapshot.test.ts b/uapi/tests/terminalTransactionDetailSnapshot.test.ts index 3f33308e4..78840c091 100644 --- a/uapi/tests/terminalTransactionDetailSnapshot.test.ts +++ b/uapi/tests/terminalTransactionDetailSnapshot.test.ts @@ -207,9 +207,9 @@ describe('terminal-transaction-detail-snapshot helpers', () => { canonLabel: 'Bitcode active posture', readReview: { id: 'read-review', - label: 'Read review before fit search', - summary: 'Measured Read accepted for source-to-shares fit search.', - metrics: [{ label: 'Fit search admitted', value: 'yes' }], + label: 'Read review before Finding Fits', + summary: 'Measured Read accepted for source-to-shares Finding Fits.', + metrics: [{ label: 'Finding Fits admitted', value: 'yes' }], rows: [ { label: 'Review stage', value: 'post-measurement-pre-fit' }, { label: 'Protocol focus', value: 'source-to-shares' }, @@ -387,9 +387,9 @@ describe('terminal-transaction-detail-snapshot helpers', () => { canonLabel: 'Bitcode active posture', readReview: { id: 'read-review', - label: 'Read review before fit search', - summary: 'Measured Read accepted for source-to-shares fit search.', - metrics: [{ label: 'Fit search admitted', value: 'yes' }], + label: 'Read review before Finding Fits', + summary: 'Measured Read accepted for source-to-shares Finding Fits.', + metrics: [{ label: 'Finding Fits admitted', value: 'yes' }], rows: [ { label: 'Review stage', value: 'post-measurement-pre-fit' }, { label: 'Protocol focus', value: 'source-to-shares' }, diff --git a/uapi/tsconfig.json b/uapi/tsconfig.json index ed23da024..7c2094420 100644 --- a/uapi/tsconfig.json +++ b/uapi/tsconfig.json @@ -40,12 +40,18 @@ "@bitcode/prompts/raw_promptparts/*": [ "../packages/prompts/src/raw_promptparts/*" ], + "@bitcode/btd/terminal-operational-health": [ + "../packages/btd/src/terminal-operational-health.ts" + ], "@bitcode/pipeline-asset-pack": [ "../packages/pipelines/asset-pack/src/index.ts" ], "@bitcode/pipeline-asset-pack/read-need": [ "../packages/pipelines/asset-pack/src/read-need.ts" ], + "@bitcode/pipeline-asset-pack/reading-pipeline-contract": [ + "../packages/pipelines/asset-pack/src/reading-pipeline-contract.ts" + ], "@bitcode/*": [ "../packages/*/src", "../packages/*"