From daaa4200602ccb79e8fd35b8fc1dcac56575aea3 Mon Sep 17 00:00:00 2001 From: Garrett Maring Date: Thu, 28 May 2026 17:41:40 -0300 Subject: [PATCH] V42 Gate 3: Add Reading Shortest Path State Machine Implement the source-safe Reading route state contract with recoverable transaction ids, readingStage hydration, retry/restart posture, failure repair actions, and Terminal route projection. Add the V42 Reading shortest path artifact, generator, checker, protocol export/test, workflow wiring, and documentation/parity updates, including V43+ UX route-renaming scope for Packs, Read, and Deposit. --- .bitcode/v42-depositing-shortest-path.json | 2 +- ...2-reading-shortest-path-state-machine.json | 575 ++++++++++++++++++ .github/workflows/bitcode-canon-quality.yml | 3 + .github/workflows/bitcode-gate-quality.yml | 3 + BITCODE_SPEC_V42.md | 11 +- BITCODE_SPEC_V42_DELTA.md | 3 + BITCODE_SPEC_V42_NOTES.md | 5 + BITCODE_SPEC_V42_PARITY_MATRIX.md | 4 +- README.md | 9 +- SPECIFICATIONS_ROADMAP.md | 7 +- package.json | 3 + packages/protocol/README.md | 9 + .../canonical/v42-depositing-shortest-path.js | 6 +- ...v42-reading-shortest-path-state-machine.js | 324 ++++++++++ packages/protocol/src/index.d.ts | 9 + packages/protocol/src/index.js | 11 + ...eading-shortest-path-state-machine.test.js | 77 +++ ...e3-reading-shortest-path-state-machine.mjs | 240 ++++++++ ...42-reading-shortest-path-state-machine.mjs | 31 + uapi/app/terminal/README.md | 10 + .../terminal/TerminalDepositReadWorkbench.tsx | 17 +- uapi/app/terminal/TerminalPageClient.tsx | 1 + .../terminal-enterprise-reading-ux-state.ts | 116 +++- .../terminalEnterpriseReadingUxState.test.ts | 37 ++ 24 files changed, 1502 insertions(+), 11 deletions(-) create mode 100644 .bitcode/v42-reading-shortest-path-state-machine.json create mode 100644 packages/protocol/src/canonical/v42-reading-shortest-path-state-machine.js create mode 100644 packages/protocol/test/v42-reading-shortest-path-state-machine.test.js create mode 100644 scripts/check-v42-gate3-reading-shortest-path-state-machine.mjs create mode 100644 scripts/generate-v42-reading-shortest-path-state-machine.mjs diff --git a/.bitcode/v42-depositing-shortest-path.json b/.bitcode/v42-depositing-shortest-path.json index d6833815..e8a84d7f 100644 --- a/.bitcode/v42-depositing-shortest-path.json +++ b/.bitcode/v42-depositing-shortest-path.json @@ -424,7 +424,7 @@ "passed": true }, { - "id": "roadmap-current-gate-advanced", + "id": "roadmap-records-gate2-closure", "sourcePath": "SPECIFICATIONS_ROADMAP.md", "passed": true } diff --git a/.bitcode/v42-reading-shortest-path-state-machine.json b/.bitcode/v42-reading-shortest-path-state-machine.json new file mode 100644 index 00000000..a5f4b88e --- /dev/null +++ b/.bitcode/v42-reading-shortest-path-state-machine.json @@ -0,0 +1,575 @@ +{ + "artifactId": "v42-reading-shortest-path-state-machine", + "schemaId": "bitcode.v42.readingShortestPathStateMachine.v1", + "version": "V42", + "currentTarget": "V41", + "sourceSafetyVerdict": "source-safe-reading-shortest-path-state-machine-metadata", + "generatedAt": "deterministic", + "artifactRoot": "v42-reading-shortest-path-state-machine:c30584924864342dafe806b5", + "passed": true, + "rows": [ + { + "rowId": "state:five-step-shortest-path", + "purpose": "Preserve exactly five enterprise Reading steps from Read Request through Need review, Finding Fits, AssetPack preview, and settlement delivery.", + "sourceRoots": [ + "uapi/app/terminal/terminal-enterprise-reading-ux-state.ts", + "uapi/app/terminal/TerminalDepositReadWorkbench.tsx" + ], + "emittedTypes": [ + "TerminalEnterpriseReadingUxState", + "TerminalEnterpriseReadingStepView" + ], + "requiredEvidence": [ + "request-read", + "review-synthesized-need", + "request-fit", + "review-synthesized-asset-pack", + "buy-asset-pack-settle" + ], + "rowRoot": "v42-reading-shortest-path-state-machine-row:f5bccf679eaefd791e5f0c19", + "sourceSafetyClass": "source_safe_reading_shortest_path_state_machine_metadata", + "sourceSafeMetadataOnly": true, + "lowDetailDefault": true, + "expandableSourceSafeDetail": true, + "protectedSourceVisible": false, + "rawProtectedPromptVisible": false, + "rawProviderResponseVisible": false, + "unpaidAssetPackSourceVisible": false, + "walletPrivateMaterialVisible": false, + "settlementPrivatePayloadVisible": false, + "ledgerAuthorityClaimed": false, + "forbiddenPayloadClasses": [ + "protected-source-payloads", + "raw-protected-prompts", + "raw-provider-responses", + "unpaid-assetpack-source", + "wallet-private-material", + "settlement-private-payloads", + "ledger-write-authority", + "secret-values" + ] + }, + { + "rowId": "route:transaction-stage-persistence", + "purpose": "Bind Reading state to recoverable transaction ids and readingStage route state so refresh, restart, and route handoff preserve the active stage.", + "sourceRoots": [ + "uapi/app/terminal/terminal-enterprise-reading-ux-state.ts", + "uapi/app/terminal/terminal-transaction-query.ts", + "uapi/app/terminal/TerminalPageClient.tsx", + "uapi/app/terminal/TerminalDepositReadWorkbench.tsx" + ], + "emittedTypes": [ + "TerminalEnterpriseReadingRouteState", + "TerminalConversationHandoffContext.readingStage" + ], + "requiredEvidence": [ + "transactionIdRequiredForRecovery", + "readingStageQueryParam", + "activeStageHydratedFromRoute" + ], + "rowRoot": "v42-reading-shortest-path-state-machine-row:8d27b75c5cb5dc6528241458", + "sourceSafetyClass": "source_safe_reading_shortest_path_state_machine_metadata", + "sourceSafeMetadataOnly": true, + "lowDetailDefault": true, + "expandableSourceSafeDetail": true, + "protectedSourceVisible": false, + "rawProtectedPromptVisible": false, + "rawProviderResponseVisible": false, + "unpaidAssetPackSourceVisible": false, + "walletPrivateMaterialVisible": false, + "settlementPrivatePayloadVisible": false, + "ledgerAuthorityClaimed": false, + "forbiddenPayloadClasses": [ + "protected-source-payloads", + "raw-protected-prompts", + "raw-provider-responses", + "unpaid-assetpack-source", + "wallet-private-material", + "settlement-private-payloads", + "ledger-write-authority", + "secret-values" + ] + }, + { + "rowId": "transition:accepted-need-before-finding-fits", + "purpose": "Make accepted Need the hard transition before Finding Fits, preview, settlement, or delivery can proceed.", + "sourceRoots": [ + "uapi/app/terminal/terminal-enterprise-reading-ux-state.ts", + "uapi/app/terminal/TerminalDepositReadWorkbench.tsx", + "uapi/app/conversations/conversation-terminal-handoff.ts" + ], + "emittedTypes": [ + "acceptedNeedRequiredBeforeFindingFits", + "TerminalReadNeedState" + ], + "requiredEvidence": [ + "accepted Need required", + "ReadNeedComprehensionSynthesis", + "ReadFitsFindingSynthesis" + ], + "rowRoot": "v42-reading-shortest-path-state-machine-row:f9d95c5e18d4566c693e32c1", + "sourceSafetyClass": "source_safe_reading_shortest_path_state_machine_metadata", + "sourceSafeMetadataOnly": true, + "lowDetailDefault": true, + "expandableSourceSafeDetail": true, + "protectedSourceVisible": false, + "rawProtectedPromptVisible": false, + "rawProviderResponseVisible": false, + "unpaidAssetPackSourceVisible": false, + "walletPrivateMaterialVisible": false, + "settlementPrivatePayloadVisible": false, + "ledgerAuthorityClaimed": false, + "forbiddenPayloadClasses": [ + "protected-source-payloads", + "raw-protected-prompts", + "raw-provider-responses", + "unpaid-assetpack-source", + "wallet-private-material", + "settlement-private-payloads", + "ledger-write-authority", + "secret-values" + ] + }, + { + "rowId": "retry:restart-and-failure-repair", + "purpose": "Represent retry, restart, and failure repair posture as source-safe state metadata without exposing protected source, prompts, provider responses, wallet material, or settlement payloads.", + "sourceRoots": [ + "uapi/app/terminal/terminal-enterprise-reading-ux-state.ts", + "uapi/tests/terminalEnterpriseReadingUxState.test.ts", + "uapi/app/terminal/TerminalDepositReadWorkbench.tsx" + ], + "emittedTypes": [ + "TerminalEnterpriseReadingFailureKind", + "TerminalEnterpriseReadingRouteState.failureRepairActions" + ], + "requiredEvidence": [ + "retryPreservesNeedLineage", + "restartRestoresActiveStage", + "failureRepairActions" + ], + "rowRoot": "v42-reading-shortest-path-state-machine-row:8255affa47cdbf93089ab9cc", + "sourceSafetyClass": "source_safe_reading_shortest_path_state_machine_metadata", + "sourceSafeMetadataOnly": true, + "lowDetailDefault": true, + "expandableSourceSafeDetail": true, + "protectedSourceVisible": false, + "rawProtectedPromptVisible": false, + "rawProviderResponseVisible": false, + "unpaidAssetPackSourceVisible": false, + "walletPrivateMaterialVisible": false, + "settlementPrivatePayloadVisible": false, + "ledgerAuthorityClaimed": false, + "forbiddenPayloadClasses": [ + "protected-source-payloads", + "raw-protected-prompts", + "raw-provider-responses", + "unpaid-assetpack-source", + "wallet-private-material", + "settlement-private-payloads", + "ledger-write-authority", + "secret-values" + ] + }, + { + "rowId": "ui:low-detail-proof-on-expand", + "purpose": "Keep the default Reading view guided and low-detail while details expand to source-safe proof roots, measurements, blockers, and visible field ids.", + "sourceRoots": [ + "uapi/app/terminal/terminal-enterprise-reading-ux-state.ts", + "uapi/app/terminal/TerminalDepositReadWorkbench.tsx", + "uapi/app/terminal/README.md" + ], + "emittedTypes": [ + "TerminalEnterpriseReadingUxState.disclosure" + ], + "requiredEvidence": [ + "lowDetailDefault", + "expandableSourceSafeDetail", + "Source-safe detail" + ], + "rowRoot": "v42-reading-shortest-path-state-machine-row:bc5d512f5a40eab14e0cc0a5", + "sourceSafetyClass": "source_safe_reading_shortest_path_state_machine_metadata", + "sourceSafeMetadataOnly": true, + "lowDetailDefault": true, + "expandableSourceSafeDetail": true, + "protectedSourceVisible": false, + "rawProtectedPromptVisible": false, + "rawProviderResponseVisible": false, + "unpaidAssetPackSourceVisible": false, + "walletPrivateMaterialVisible": false, + "settlementPrivatePayloadVisible": false, + "ledgerAuthorityClaimed": false, + "forbiddenPayloadClasses": [ + "protected-source-payloads", + "raw-protected-prompts", + "raw-provider-responses", + "unpaid-assetpack-source", + "wallet-private-material", + "settlement-private-payloads", + "ledger-write-authority", + "secret-values" + ] + }, + { + "rowId": "stream:rich-reading-pipeline-telemetry", + "purpose": "Keep Reading pipeline progress inspectable through rich execution stream rows for phase, PTRR step, ThricifiedGeneration, tool, prompt-template id, output schema, and parsed-result posture.", + "sourceRoots": [ + "uapi/app/terminal/terminal-pipeline-harness-client.ts", + "uapi/tests/pipelineExecutionLogHeader.test.tsx", + "uapi/app/terminal/TerminalDepositReadWorkbench.tsx" + ], + "emittedTypes": [ + "TerminalReadFitsFindingSynthesisHarnessEvent", + "ExecutionLogItem" + ], + "requiredEvidence": [ + "pipelinePhaseId", + "ptrrStepId", + "thricifiedGenerationId", + "promptTemplateId", + "outputSchema" + ], + "rowRoot": "v42-reading-shortest-path-state-machine-row:f84a65471fa1a4ff2dc6a8be", + "sourceSafetyClass": "source_safe_reading_shortest_path_state_machine_metadata", + "sourceSafeMetadataOnly": true, + "lowDetailDefault": true, + "expandableSourceSafeDetail": true, + "protectedSourceVisible": false, + "rawProtectedPromptVisible": false, + "rawProviderResponseVisible": false, + "unpaidAssetPackSourceVisible": false, + "walletPrivateMaterialVisible": false, + "settlementPrivatePayloadVisible": false, + "ledgerAuthorityClaimed": false, + "forbiddenPayloadClasses": [ + "protected-source-payloads", + "raw-protected-prompts", + "raw-provider-responses", + "unpaid-assetpack-source", + "wallet-private-material", + "settlement-private-payloads", + "ledger-write-authority", + "secret-values" + ] + }, + { + "rowId": "activity:history-and-workbench-readback", + "purpose": "Project Reading state through activity history and workbench readback so transaction detail, proof roots, and compensation/settlement posture remain recoverable.", + "sourceRoots": [ + "uapi/app/terminal/terminal-activity-history.ts", + "uapi/app/terminal/TerminalDepositReadWorkbench.tsx", + "uapi/app/terminal/terminal-deposit-read-workbench.ts" + ], + "emittedTypes": [ + "WorkspaceRun", + "TerminalDepositReadWorkbench" + ], + "requiredEvidence": [ + "assetPackCompletion", + "TerminalDepositedSourceRevision", + "sourceRevision" + ], + "rowRoot": "v42-reading-shortest-path-state-machine-row:96ceba0e8af3d5ad27fcab3c", + "sourceSafetyClass": "source_safe_reading_shortest_path_state_machine_metadata", + "sourceSafeMetadataOnly": true, + "lowDetailDefault": true, + "expandableSourceSafeDetail": true, + "protectedSourceVisible": false, + "rawProtectedPromptVisible": false, + "rawProviderResponseVisible": false, + "unpaidAssetPackSourceVisible": false, + "walletPrivateMaterialVisible": false, + "settlementPrivatePayloadVisible": false, + "ledgerAuthorityClaimed": false, + "forbiddenPayloadClasses": [ + "protected-source-payloads", + "raw-protected-prompts", + "raw-provider-responses", + "unpaid-assetpack-source", + "wallet-private-material", + "settlement-private-payloads", + "ledger-write-authority", + "secret-values" + ] + }, + { + "rowId": "tests:route-state-contracts", + "purpose": "Prove the state machine through route-state tests, component state tests, Conversation handoff tests, workbench tests, and stream-header tests.", + "sourceRoots": [ + "uapi/tests/terminalEnterpriseReadingUxState.test.ts", + "uapi/tests/terminalDepositReadWorkbench.test.ts", + "uapi/tests/conversationTerminalHandoff.test.tsx", + "uapi/tests/terminalTransactionQuery.test.ts", + "uapi/tests/pipelineExecutionLogHeader.test.tsx", + ".github/workflows/bitcode-gate-quality.yml" + ], + "emittedTypes": [ + "V42ReadingShortestPathStateMachineReport" + ], + "requiredEvidence": [ + "terminal-enterprise-reading-ux-state", + "readingStage=request-fit", + "Browser proof Terminal cockpit" + ], + "rowRoot": "v42-reading-shortest-path-state-machine-row:e06bfe55a25e9021a4fb3780", + "sourceSafetyClass": "source_safe_reading_shortest_path_state_machine_metadata", + "sourceSafeMetadataOnly": true, + "lowDetailDefault": true, + "expandableSourceSafeDetail": true, + "protectedSourceVisible": false, + "rawProtectedPromptVisible": false, + "rawProviderResponseVisible": false, + "unpaidAssetPackSourceVisible": false, + "walletPrivateMaterialVisible": false, + "settlementPrivatePayloadVisible": false, + "ledgerAuthorityClaimed": false, + "forbiddenPayloadClasses": [ + "protected-source-payloads", + "raw-protected-prompts", + "raw-provider-responses", + "unpaid-assetpack-source", + "wallet-private-material", + "settlement-private-payloads", + "ledger-write-authority", + "secret-values" + ] + }, + { + "rowId": "spec:v42-gate3-closure", + "purpose": "Bind V42 Gate 3 to SPEC, DELTA, NOTES, PARITY, roadmap, README, workflow, generated artifact, and checker closure.", + "sourceRoots": [ + "BITCODE_SPEC_V42.md", + "BITCODE_SPEC_V42_DELTA.md", + "BITCODE_SPEC_V42_NOTES.md", + "BITCODE_SPEC_V42_PARITY_MATRIX.md", + "SPECIFICATIONS_ROADMAP.md", + "README.md", + "packages/protocol/README.md" + ], + "emittedTypes": [ + "V42ReadingShortestPathStateMachineReport" + ], + "requiredEvidence": [ + "V42 Gate 3", + "reading shortest path state machine", + "check:v42-gate3" + ], + "rowRoot": "v42-reading-shortest-path-state-machine-row:f0bc898ce7bb6757c5924aa9", + "sourceSafetyClass": "source_safe_reading_shortest_path_state_machine_metadata", + "sourceSafeMetadataOnly": true, + "lowDetailDefault": true, + "expandableSourceSafeDetail": true, + "protectedSourceVisible": false, + "rawProtectedPromptVisible": false, + "rawProviderResponseVisible": false, + "unpaidAssetPackSourceVisible": false, + "walletPrivateMaterialVisible": false, + "settlementPrivatePayloadVisible": false, + "ledgerAuthorityClaimed": false, + "forbiddenPayloadClasses": [ + "protected-source-payloads", + "raw-protected-prompts", + "raw-provider-responses", + "unpaid-assetpack-source", + "wallet-private-material", + "settlement-private-payloads", + "ledger-write-authority", + "secret-values" + ] + } + ], + "rowIds": [ + "state:five-step-shortest-path", + "route:transaction-stage-persistence", + "transition:accepted-need-before-finding-fits", + "retry:restart-and-failure-repair", + "ui:low-detail-proof-on-expand", + "stream:rich-reading-pipeline-telemetry", + "activity:history-and-workbench-readback", + "tests:route-state-contracts", + "spec:v42-gate3-closure" + ], + "stepIds": [ + "request-read", + "review-synthesized-need", + "request-fit", + "review-synthesized-asset-pack", + "buy-asset-pack-settle" + ], + "predicateResults": [ + { + "id": "ux-state-keeps-five-step-path", + "sourcePath": "uapi/app/terminal/terminal-enterprise-reading-ux-state.ts", + "passed": true + }, + { + "id": "ux-state-defines-route-state", + "sourcePath": "uapi/app/terminal/terminal-enterprise-reading-ux-state.ts", + "passed": true + }, + { + "id": "ux-state-defines-retry-failure-source-safety", + "sourcePath": "uapi/app/terminal/terminal-enterprise-reading-ux-state.ts", + "passed": true + }, + { + "id": "ux-state-forbids-protected-payloads", + "sourcePath": "uapi/app/terminal/terminal-enterprise-reading-ux-state.ts", + "passed": true + }, + { + "id": "terminal-page-passes-reading-stage", + "sourcePath": "uapi/app/terminal/TerminalPageClient.tsx", + "passed": true + }, + { + "id": "workbench-projects-route-state", + "sourcePath": "uapi/app/terminal/TerminalDepositReadWorkbench.tsx", + "passed": true + }, + { + "id": "workbench-keeps-low-detail-expandable-cards", + "sourcePath": "uapi/app/terminal/TerminalDepositReadWorkbench.tsx", + "passed": true + }, + { + "id": "workbench-contract-reexports-stage-ids", + "sourcePath": "uapi/app/terminal/terminal-deposit-read-workbench.ts", + "passed": true + }, + { + "id": "terminal-query-reads-reading-stage", + "sourcePath": "uapi/app/terminal/terminal-transaction-query.ts", + "passed": true + }, + { + "id": "activity-history-keeps-reading-readback", + "sourcePath": "uapi/app/terminal/terminal-activity-history.ts", + "passed": true + }, + { + "id": "harness-projects-rich-reading-telemetry", + "sourcePath": "uapi/app/terminal/terminal-pipeline-harness-client.ts", + "passed": true + }, + { + "id": "conversation-handoff-preserves-reading-stage", + "sourcePath": "uapi/app/conversations/conversation-terminal-handoff.ts", + "passed": true + }, + { + "id": "ux-state-tests-cover-route-retry-failure", + "sourcePath": "uapi/tests/terminalEnterpriseReadingUxState.test.ts", + "passed": true + }, + { + "id": "workbench-tests-cover-five-stage-labels", + "sourcePath": "uapi/tests/terminalDepositReadWorkbench.test.ts", + "passed": true + }, + { + "id": "handoff-tests-cover-reading-stage-route", + "sourcePath": "uapi/tests/conversationTerminalHandoff.test.tsx", + "passed": true + }, + { + "id": "query-tests-cover-reading-stage-route", + "sourcePath": "uapi/tests/terminalTransactionQuery.test.ts", + "passed": true + }, + { + "id": "stream-tests-cover-rich-header", + "sourcePath": "uapi/tests/pipelineExecutionLogHeader.test.tsx", + "passed": true + }, + { + "id": "workflow-wires-gate3-check", + "sourcePath": ".github/workflows/bitcode-gate-quality.yml", + "passed": true + }, + { + "id": "v42-spec-gate3-expanded", + "sourcePath": "BITCODE_SPEC_V42.md", + "passed": true + }, + { + "id": "v42-delta-gate3-expanded", + "sourcePath": "BITCODE_SPEC_V42_DELTA.md", + "passed": true + }, + { + "id": "v42-notes-gate3-expanded", + "sourcePath": "BITCODE_SPEC_V42_NOTES.md", + "passed": true + }, + { + "id": "v42-parity-gate3-implemented", + "sourcePath": "BITCODE_SPEC_V42_PARITY_MATRIX.md", + "passed": true + }, + { + "id": "roadmap-records-gate3-closure", + "sourcePath": "SPECIFICATIONS_ROADMAP.md", + "passed": true + }, + { + "id": "readmes-document-gate3", + "sourcePath": "README.md", + "passed": true + } + ], + "coverage": { + "rowCount": 9, + "stepCount": 5, + "acceptedUserPath": [ + "request-read", + "review-synthesized-need", + "request-finding-fits", + "review-source-safe-assetpack-preview", + "buy-settle-and-deliver-assetpack" + ], + "requiredPredicateCount": 24, + "passedPredicateCount": 24, + "failedPredicateIds": [], + "routePersistenceCovered": true, + "transactionIdRecoveryCovered": true, + "restartRetryFailureCovered": true, + "acceptedNeedGateCovered": true, + "streamLogIntegrationCovered": true, + "componentRouteTestsCovered": true, + "sourceSafeMetadataOnly": true, + "lowDetailDefault": true, + "expandableSourceSafeDetail": true, + "protectedSourceVisible": false, + "rawProtectedPromptVisible": false, + "rawProviderResponseVisible": false, + "unpaidAssetPackSourceVisible": false, + "walletPrivateMaterialVisible": false, + "settlementPrivatePayloadVisible": false, + "ledgerAuthorityClaimed": false, + "legacySourceRoots": false + }, + "sourceRoots": { + "terminalUxState": "uapi/app/terminal/terminal-enterprise-reading-ux-state.ts", + "terminalWorkbench": "uapi/app/terminal/TerminalDepositReadWorkbench.tsx", + "terminalPageClient": "uapi/app/terminal/TerminalPageClient.tsx", + "terminalWorkbenchContract": "uapi/app/terminal/terminal-deposit-read-workbench.ts", + "terminalRouteQuery": "uapi/app/terminal/terminal-transaction-query.ts", + "terminalActivityHistory": "uapi/app/terminal/terminal-activity-history.ts", + "terminalHarnessClient": "uapi/app/terminal/terminal-pipeline-harness-client.ts", + "conversationHandoff": "uapi/app/conversations/conversation-terminal-handoff.ts", + "uxStateTest": "uapi/tests/terminalEnterpriseReadingUxState.test.ts", + "workbenchTest": "uapi/tests/terminalDepositReadWorkbench.test.ts", + "handoffTest": "uapi/tests/conversationTerminalHandoff.test.tsx", + "queryTest": "uapi/tests/terminalTransactionQuery.test.ts", + "streamHeaderTest": "uapi/tests/pipelineExecutionLogHeader.test.tsx", + "gateWorkflow": ".github/workflows/bitcode-gate-quality.yml", + "v42Spec": "BITCODE_SPEC_V42.md", + "v42Delta": "BITCODE_SPEC_V42_DELTA.md", + "v42Notes": "BITCODE_SPEC_V42_NOTES.md", + "v42Parity": "BITCODE_SPEC_V42_PARITY_MATRIX.md", + "roadmap": "SPECIFICATIONS_ROADMAP.md", + "rootReadme": "README.md", + "terminalReadme": "uapi/app/terminal/README.md", + "protocolReadme": "packages/protocol/README.md" + } +} diff --git a/.github/workflows/bitcode-canon-quality.yml b/.github/workflows/bitcode-canon-quality.yml index 05484b61..dc9f130b 100644 --- a/.github/workflows/bitcode-canon-quality.yml +++ b/.github/workflows/bitcode-canon-quality.yml @@ -298,6 +298,9 @@ jobs: if [ -f scripts/check-v42-gate2-depositing-shortest-path.mjs ]; then node scripts/check-v42-gate2-depositing-shortest-path.mjs --skip-branch-check --skip-package-tests fi + if [ -f scripts/check-v42-gate3-reading-shortest-path-state-machine.mjs ]; then + node scripts/check-v42-gate3-reading-shortest-path-state-machine.mjs --skip-branch-check --skip-uapi-tests + fi fi else echo "Unexpected BITCODE_SPEC.txt pointer: $POINTER" >&2 diff --git a/.github/workflows/bitcode-gate-quality.yml b/.github/workflows/bitcode-gate-quality.yml index 40c215f9..bf2440ee 100644 --- a/.github/workflows/bitcode-gate-quality.yml +++ b/.github/workflows/bitcode-gate-quality.yml @@ -427,6 +427,9 @@ jobs: if [ -f scripts/check-v42-gate2-depositing-shortest-path.mjs ]; then node scripts/check-v42-gate2-depositing-shortest-path.mjs --skip-branch-check --skip-package-tests fi + if [ -f scripts/check-v42-gate3-reading-shortest-path-state-machine.mjs ]; then + node scripts/check-v42-gate3-reading-shortest-path-state-machine.mjs --skip-branch-check --skip-uapi-tests + fi fi else echo "Unexpected BITCODE_SPEC.txt pointer: $POINTER" >&2 diff --git a/BITCODE_SPEC_V42.md b/BITCODE_SPEC_V42.md index 71339f89..7b85d98f 100644 --- a/BITCODE_SPEC_V42.md +++ b/BITCODE_SPEC_V42.md @@ -118,6 +118,15 @@ Gate 3 must make the five Reading steps a coherent product state machine: reques It must simplify default Terminal experience while preserving expandable proof, execution, telemetry, ledger, and storage detail. It must prove route persistence, transaction ids, stage transitions, restart/retry behavior, source-safe UI rendering, and failure states. +Gate 3 implements that state machine through `TerminalEnterpriseReadingUxState` and `TerminalEnterpriseReadingRouteState`. +The route-owned state carries a recoverable transaction id, `readingStage` stage intent, retry and restart posture, source-safe failure kind, and repair action metadata. +Route state may hydrate the active stage on refresh or Conversation handoff, but it does not waive blockers: Finding Fits still requires accepted Need, source-safe AssetPack preview still gates settlement, and delivery still requires settlement readback. + +Gate 3 proof artifact: `.bitcode/v42-reading-shortest-path-state-machine.json`. +This is the canonical V42 reading shortest path state machine artifact for the MVP Reading route contract. +The artifact must prove nine source-safe rows: five-step Reading path, transaction/stage route persistence, accepted-Need transition, restart/retry/failure repair, low-detail proof-on-expand UI, rich Reading pipeline telemetry, activity/workbench readback, route/component/stream tests, and SPEC/DELTA/NOTES/PARITY/README/workflow closure. +Validating command: `pnpm run check:v42-gate3`. + ## V42 Gate 4 ReadNeed Review And Resynthesis Product Closure Gate 4 must make `ReadNeedComprehensionSynthesis` product-ready in the MVP flow. @@ -413,7 +422,7 @@ V42 inherits operator-quality expectations for browser proof, accessibility, vis | `.bitcode/v42-canonical-input-report.json` | `check-bitcode-canonical-inputs` | source-safe metadata | canonical input proof | | `.bitcode/v42-canon-posture-drift-report.json` | `check-bitcode-canon-posture-drift` | source-safe metadata | active V41 / draft V42 posture proof | | `.bitcode/v42-depositing-shortest-path.json` | V42 Gate 2 | source-safe metadata | deposit MVP proof | -| `.bitcode/v42-reading-state-machine.json` | V42 Gate 3 | source-safe metadata | Reading product state proof | +| `.bitcode/v42-reading-shortest-path-state-machine.json` | V42 Gate 3 | source-safe metadata | Reading product state proof | | `.bitcode/v42-readneed-review-resynthesis.json` | V42 Gate 4 | source-safe metadata | Need review proof | | `.bitcode/v42-readfitsfinding-preview-quote.json` | V42 Gate 5 | source-safe metadata | Finding Fits, preview, and quote proof | | `.bitcode/v42-settlement-rights-delivery.json` | V42 Gate 6 | source-safe metadata | settlement/delivery proof | diff --git a/BITCODE_SPEC_V42_DELTA.md b/BITCODE_SPEC_V42_DELTA.md index cfc889e1..33f29a47 100644 --- a/BITCODE_SPEC_V42_DELTA.md +++ b/BITCODE_SPEC_V42_DELTA.md @@ -41,6 +41,8 @@ The compensation preview is deliberately pre-mint: it records BTC/source-to-shar ### Gate 3: Reading Shortest Path State Machine Implement and prove the five-step Reading product state machine with low-detail defaults and expandable proof/telemetry detail. +Gate 3 now binds route-owned Reading state to transaction recovery, `readingStage` route hydration, retry/restart posture, source-safe failure repair, accepted-Need blockers, source-safe preview blockers, rich execution stream readback, and `.bitcode/v42-reading-shortest-path-state-machine.json`. +The state machine keeps Terminal guided by default while preserving proof-on-expand detail for operators. ### Gate 4: ReadNeed Review And Resynthesis Product Closure @@ -71,6 +73,7 @@ Bind every V42 product artifact, workflow, generated proof, promotion command, s - V43+ agentic deposit AssetPack option synthesis remains deferred. - `/terminal` is not split into `/read` and `/deposit` during V42. - `/exchange` is not renamed to `/packs` during V42. +- V43+ must take the route-vocabulary cleanup seriously: `/packs` replaces Exchange as the searchable master-detail activity route, while `/read` and `/deposit` become the short core paths for buying and selling AssetPacks. - Production-mainnet value-bearing operation remains blocked unless a later promoted canon explicitly admits it. - V42 Gate 1 does not change route behavior, pipeline behavior, settlement behavior, or demonstration behavior. diff --git a/BITCODE_SPEC_V42_NOTES.md b/BITCODE_SPEC_V42_NOTES.md index 9c67e6be..3ba0c620 100644 --- a/BITCODE_SPEC_V42_NOTES.md +++ b/BITCODE_SPEC_V42_NOTES.md @@ -43,6 +43,9 @@ The V42 Reading path should be a five-step enterprise flow: The default UI should be guided and low-detail. The rich execution log, proof roots, telemetry rows, and ledger/storage details remain available on expansion. +Gate 3 implements the current V42 state-machine layer with `TerminalEnterpriseReadingRouteState`. +The recoverable route state includes transaction id presence, `readingStage`, active-stage hydration, retry and restart posture, source-safe failure kind, and repair actions. +This makes refresh, route handoff, and recovery inspectable without disclosing protected source, protected prompts, raw provider responses, unpaid AssetPack source, wallet private material, private settlement payloads, or ledger authority. ## AssetPack source-safety note @@ -66,6 +69,8 @@ The route model should be AssetPacks in and AssetPacks out: `/deposit` creates r The `/packs` master view should support column sorting, filtering, and search over measurements, synthesized AssetPack titles and descriptions, values, activity or transaction type, settlement posture, and compensation state. The detail view should expose the selected activity's source-safe data, proof roots, telemetry, ledger/database synchronization, and expandable payloads without replacing the short default path. Outside public documentation, product UX should avoid self-referential explanatory copy; route structure, concise labels, progressive detail, and proof-on-expand must make Depositing, Reading, and Pack activity self-explanatory. +The current transitional Terminal/Exchange UX remains too dense for the final MVP posture. +V43+ should explicitly own the broad UX cleanup: remove Exchange naming from route and component vocabulary in favor of Packs, split Terminal into `/read` and `/deposit`, keep `/packs` as searchable master-detail activity, and use rich themed reusable components without relying on in-product explanatory copy to compensate for unclear flows. ## Concise current-system reading diff --git a/BITCODE_SPEC_V42_PARITY_MATRIX.md b/BITCODE_SPEC_V42_PARITY_MATRIX.md index 58e561f3..86f3f222 100644 --- a/BITCODE_SPEC_V42_PARITY_MATRIX.md +++ b/BITCODE_SPEC_V42_PARITY_MATRIX.md @@ -34,7 +34,7 @@ This matrix records the reliable MVP product surfaces that must become promotion | Gate workflow | Gate quality knows active V41 / draft V42 posture and V42 Gate 1 | `.github/workflows/bitcode-gate-quality.yml` | drafted | | Canon workflow | Canon quality knows active V41 / draft V42 posture and V42 Gate 1 | `.github/workflows/bitcode-canon-quality.yml` | drafted | | Depositing shortest path | Source material can be admitted with Depository proof and compensation visibility | `.bitcode/v42-depositing-shortest-path.json`, `DepositorySupplyCompensationPreview`, `/api/deposits`, Terminal deposit readback | implemented | -| Reading state machine | Five-step Reading UX is route-owned, persistent, and source-safe | later V42 Gate 3 artifact | draft-required | +| Reading state machine | Five-step Reading UX is route-owned, persistent, and source-safe | `.bitcode/v42-reading-shortest-path-state-machine.json`, `TerminalEnterpriseReadingRouteState`, `readingStage`, route/retry/failure tests | implemented | | ReadNeed product closure | Need synthesis, review, feedback, resynthesis, and accepted-Need admission are product-ready | later V42 Gate 4 artifact | draft-required | | Finding Fits preview and quote | Many-candidate search, selected-fit provenance, source-safe preview, and quote are product-ready | later V42 Gate 5 artifact | draft-required | | Settlement and delivery | BTC/BTD settlement, rights transfer, compensation, and repository PR delivery are synchronized | later V42 Gate 6 artifact | draft-required | @@ -48,7 +48,7 @@ This matrix records the reliable MVP product surfaces that must become promotion | --- | --- | --- | | Gate 1 | Open V42 family, roadmap, docs, workflow posture, package script, and checker | drafted | | Gate 2 | Depositing shortest path and compensation visibility artifact | implemented | -| Gate 3 | Reading shortest path state machine artifact | draft-required | +| Gate 3 | Reading shortest path state machine artifact | implemented | | Gate 4 | ReadNeed review and resynthesis product closure artifact | draft-required | | Gate 5 | ReadFitsFinding AssetPack preview and quote closure artifact | draft-required | | Gate 6 | Settlement rights transfer and repository delivery closure artifact | draft-required | diff --git a/README.md b/README.md index e3512001..3a5b1077 100644 --- a/README.md +++ b/README.md @@ -93,11 +93,18 @@ Terminal compensation roots, `.bitcode/v42-depositing-shortest-path.json`, and `check:v42-gate2`. Deposit admission remains pre-mint and pre-rights transfer; BTC source-to-shares allocation is only a later paid AssetPack settlement route. +V42 Gate 3 adds the Reading shortest path state machine with +`TerminalEnterpriseReadingRouteState`, recoverable transaction ids, +`readingStage` hydration, retry/restart posture, source-safe failure repair, +accepted-Need blockers, source-safe preview blockers, +`.bitcode/v42-reading-shortest-path-state-machine.json`, and +`check:v42-gate3`. V43+ is roadmapped as the later agentic depositing evolution: repository agents synthesize deposit AssetPack options from connected enterprise code, Depository state, and Reading demand; enterprises approve or reject sub-critical positive-ROI options; `/terminal` separates into `/read` and -`/deposit`; and `/exchange` is renamed to `/packs` across product naming. +`/deposit`; and `/exchange` is renamed to `/packs` across routes, component +prefixes, tests, docs, and product naming. That future route model treats AssetPacks as the product object in and out: `/deposit` proposes deposit AssetPack options from source and Bitcode demand, `/read` buys synthesized Need-Fit AssetPacks, and `/packs` becomes the diff --git a/SPECIFICATIONS_ROADMAP.md b/SPECIFICATIONS_ROADMAP.md index de22cd64..f328daae 100644 --- a/SPECIFICATIONS_ROADMAP.md +++ b/SPECIFICATIONS_ROADMAP.md @@ -5,11 +5,12 @@ - Current active canonical pointer: `BITCODE_SPEC.txt` -> `V41` - Current active canon: `BITCODE_SPEC_V41.md` - Current draft target: `BITCODE_SPEC_V42.md`. -- Current working gate: V42 Gate 2 Depositing Shortest Path And Compensation Visibility. -- Next queued gate after V42 Gate 2: V42 Gate 3 Reading Shortest Path State Machine. +- Current working gate: V42 Gate 3 Reading Shortest Path State Machine. +- Next queued gate after V42 Gate 3: V42 Gate 4 ReadNeed Review And Resynthesis Product Closure. - Latest closed version: V41 Prompt And PromptPart Excellence, which promoted PromptPart and Prompt inventory, registry interpolation contracts, Reading prompt baselines, ReadNeedComprehensionSynthesis prompt hardening, ReadFitsFindingSynthesis prompt hardening, Conversation/tool/interface prompt rewrite, prompt benchmark telemetry, and V41 promotion readiness. - Recent V42 opening anchor: reliable MVP experience opens over promoted V41 with V42 SPEC, DELTA, NOTES, and PARITY files, `check:v42-gate1`, active V41 / draft V42 posture, and a nine-gate plan for shortest-path Depositing, five-step Reading, ReadNeed product closure, ReadFitsFinding preview and quote closure, settlement and repository delivery, AI-reading demonstration, local/staging rehearsal, and promotion readiness. - V42 Gate 2 closure anchor: reliable MVP experience now owns source-safe Depositing compensation visibility through `DepositorySupplyCompensationPreview`, deposit route `depositoryEvidence.compensationPreview`, deterministic `.bitcode/v42-depositing-shortest-path.json`, route/API readiness checks, source validation, Depository search/vector/storage projection, source-to-shares compensation readback keys, Terminal compensation roots, focused package/protocol tests, workflow wiring, and `check:v42-gate2`. +- V42 Gate 3 closure anchor: reliable MVP experience now owns the five-step Reading shortest path state machine through `TerminalEnterpriseReadingUxState`, `TerminalEnterpriseReadingRouteState`, recoverable transaction ids, `readingStage` route hydration, retry/restart posture, source-safe failure repair actions, accepted-Need blockers, source-safe preview blockers, rich Reading pipeline telemetry readback, deterministic `.bitcode/v42-reading-shortest-path-state-machine.json`, focused route/component/protocol tests, workflow wiring, and `check:v42-gate3`. - Recent V41 closure anchor: V41 canonical promotion updated `BITCODE_SPEC.txt` to `V41`, generated `BITCODE_SPEC_V41_PROVEN.md`, preserved active V41 / draft V42 runtime posture, and closed prompt-program excellence canon. - Recent V40 closure anchor: V40 canonical promotion updated `BITCODE_SPEC.txt` to `V40`, generated `BITCODE_SPEC_V40_PROVEN.md`, preserved active V40 / draft V41 runtime posture, and closed exhaustive commercial application testing canon. - Recent V39 closure anchor: V39 canonical promotion updated `BITCODE_SPEC.txt` to `V39`, generated `BITCODE_SPEC_V39_PROVEN.md`, preserved active V39 / draft V40 runtime posture, and closed commercial Reading readiness canon. @@ -160,7 +161,7 @@ They are referenced here for specification history only; active implementation w | V40 | `BITCODE_SPEC_V40.md` | promoted historical Bitcode canon | Exhaustive commercial application testing depth after V39: rich browser E2E for all website interactions and state possibilities, visual/screenshot comparison coverage, API and integration suites for pipelines, conversations, routes, ledger/database/storage synchronization, unit coverage for packages, primitives, isolated implementations, real commercial implementations, local/staging rehearsal automation, prompt benchmark smoke, and promotion readiness. | | V41 | `BITCODE_SPEC_V41.md` | active canon | Prompt and PromptPart excellence, treating prompts as programs: audit every raw PromptPart and composed Prompt, run and harden benchmarks, repartition prompts into meaningfully benchmarkable semantic parts, retitle and rewrite PromptParts/Prompts where optimal, catalogue registry bindings, interpolation contracts, benchmark fixtures, benchmark outputs, inference callsites, and parsed return types, and elevate all Reading and Conversation inference points after V38 inference scaffolding and V40 testing depth make that work measurable. The primary surface is Reading, especially `ReadNeedComprehensionSynthesis` and `ReadFitsFindingSynthesis`; conversations and other inference prompts follow with the same benchmarkable catalogue discipline. | | V42 | `BITCODE_SPEC_V42.md` | active draft target | Reliable MVP experience after V41 prompt hardening: shortest-path Depositing for any source material with Depository admission proof and later BTC compensation when deposits contribute to synthesized AssetPacks; shortest-path Reading for Read Request submission, synthesized Need review/resynthesis, Finding Fits, source-safe AssetPack measurement/preview review, BTD/BTC purchase and settlement, and repository delivery; and an AI-reading dominant demonstration proving an AssetPack can improve an AI system's training, prompt/context, or evaluation performance beyond public-data-only baselines using proprietary or otherwise non-public deposit and read materials. | -| V43+ | future specification family | planned future | Agentic enterprise deposit-side AssetPack option synthesis after the MVP paths are reliable: repository-installed Bitcode Agents compare a connected enterprise codebase, the Bitcode Depository, and Reading activity to propose deposit AssetPack options for review; filter out critical IP; estimate positive ROI against development cost and likely demand; let enterprises approve/reject options for Depository admission; split `/terminal` into `/read` and `/deposit`; and rename `/exchange` to `/packs` throughout product routes, code naming, docs, and operator vocabulary. `/packs` becomes the searchable master-detail activity surface for all pack activity, while `/deposit` and `/read` become the short core paths into and out of the Depository. | +| V43+ | future specification family | planned future | Agentic enterprise deposit-side AssetPack option synthesis and broad UX cleanup after the MVP paths are reliable: repository-installed Bitcode Agents compare a connected enterprise codebase, the Bitcode Depository, and Reading activity to propose deposit AssetPack options for review; filter out critical IP; estimate positive ROI against development cost and likely demand; let enterprises approve/reject options for Depository admission; split `/terminal` into `/read` and `/deposit`; and rename `/exchange` to `/packs` throughout product routes, code naming, docs, component prefixes, tests, and operator vocabulary. `/packs` becomes the searchable master-detail activity surface for all pack activity, with column sort/filter/search over measurements, synthesized AssetPack titles and descriptions, values, activity/transaction type, settlement posture, compensation, proof roots, and repair state. `/deposit` and `/read` become the short core paths into and out of the Depository. Outside public docs, product UX should be self-explanatory through route structure, concise labels, progressive detail, and rich reusable themed components rather than self-referential instructional copy. | ## Current Planning Spine diff --git a/package.json b/package.json index a66382ab..e2a90f91 100644 --- a/package.json +++ b/package.json @@ -307,6 +307,9 @@ "generate:v42-depositing-shortest-path": "node scripts/generate-v42-depositing-shortest-path.mjs", "check:v42-depositing-shortest-path": "node scripts/generate-v42-depositing-shortest-path.mjs --check", "check:v42-gate2": "node scripts/check-v42-gate2-depositing-shortest-path.mjs", + "generate:v42-reading-shortest-path-state-machine": "node scripts/generate-v42-reading-shortest-path-state-machine.mjs", + "check:v42-reading-shortest-path-state-machine": "node scripts/generate-v42-reading-shortest-path-state-machine.mjs --check", + "check:v42-gate3": "node scripts/check-v42-gate3-reading-shortest-path-state-machine.mjs", "generate:v38-inference-surface-inventory": "node scripts/generate-v38-inference-surface-inventory.mjs", "check:v38-inference-surface-inventory": "node scripts/generate-v38-inference-surface-inventory.mjs --check", "check:v38-gate2": "node scripts/check-v38-gate2-inference-surface-inventory.mjs", diff --git a/packages/protocol/README.md b/packages/protocol/README.md index fb299c5e..69f9b4ae 100644 --- a/packages/protocol/README.md +++ b/packages/protocol/README.md @@ -192,6 +192,15 @@ It proves deposit route readiness, Depository search/vector/storage projection, source-safe compensation preview roots, source-to-shares ledger readback keys, Terminal compensation visibility, and the pre-mint/no-pre-settlement-source boundary. +V42 Gate 3 adds the V42 Reading shortest path state machine, +`V42ReadingShortestPathStateMachine`, through +`packages/protocol/src/canonical/v42-reading-shortest-path-state-machine.js`, +`packages/protocol/test/v42-reading-shortest-path-state-machine.test.js`, +`.bitcode/v42-reading-shortest-path-state-machine.json`, and +`check:v42-gate3`. It proves the five-step Reading path, transaction/stage +route persistence, accepted-Need gating, restart/retry/failure repair, +low-detail proof-on-expand UI posture, rich Reading pipeline telemetry +readback, activity/workbench readback, and source-safe disclosure boundaries. V40 Gate 2 adds `V40TestInventoryCoverageMatrix` through `packages/protocol/src/canonical/v40-test-inventory-coverage-matrix.js`, `packages/protocol/test/v40-test-inventory-coverage-matrix.test.js`, diff --git a/packages/protocol/src/canonical/v42-depositing-shortest-path.js b/packages/protocol/src/canonical/v42-depositing-shortest-path.js index 181bc14b..46612b8d 100644 --- a/packages/protocol/src/canonical/v42-depositing-shortest-path.js +++ b/packages/protocol/src/canonical/v42-depositing-shortest-path.js @@ -217,7 +217,11 @@ function buildPredicateResults(repoRoot) { predicateResult('v42-delta-gate2-implemented', SOURCE_ROOTS.v42Delta, delta.includes('Gate 2') && delta.includes('Depository admission proof')), predicateResult('v42-notes-gate2-rehearsal', SOURCE_ROOTS.v42Notes, notes.includes('Gate 2') && notes.includes('staging-testnet')), predicateResult('v42-parity-gate2-closed', SOURCE_ROOTS.v42Parity, parity.includes('Depositing shortest path') && parity.includes('implemented')), - predicateResult('roadmap-current-gate-advanced', SOURCE_ROOTS.roadmap, roadmap.includes('Current working gate: V42 Gate 2') && roadmap.includes('V42 Gate 2 closure anchor')), + predicateResult( + 'roadmap-records-gate2-closure', + SOURCE_ROOTS.roadmap, + roadmap.includes('Current working gate: V42 Gate') && roadmap.includes('V42 Gate 2 closure anchor'), + ), ]; } diff --git a/packages/protocol/src/canonical/v42-reading-shortest-path-state-machine.js b/packages/protocol/src/canonical/v42-reading-shortest-path-state-machine.js new file mode 100644 index 00000000..f094c540 --- /dev/null +++ b/packages/protocol/src/canonical/v42-reading-shortest-path-state-machine.js @@ -0,0 +1,324 @@ +// @ts-check + +import crypto from 'node:crypto'; +import { existsSync, readFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const DEFAULT_REPO_ROOT = path.resolve(__dirname, '..', '..', '..', '..'); + +export const V42_READING_SHORTEST_PATH_STATE_MACHINE_ARTIFACT_PATH = + '.bitcode/v42-reading-shortest-path-state-machine.json'; +export const V42_READING_SHORTEST_PATH_STATE_MACHINE_SCHEMA_ID = + 'bitcode.v42.readingShortestPathStateMachine.v1'; +export const V42_READING_SHORTEST_PATH_SCHEMA_ID = + V42_READING_SHORTEST_PATH_STATE_MACHINE_SCHEMA_ID; +export const V42_READING_SHORTEST_PATH_STATE_MACHINE_VERSION = 'V42'; +export const V42_READING_SHORTEST_PATH_STATE_MACHINE_CURRENT_TARGET = 'V41'; +export const V42_READING_SHORTEST_PATH_STATE_MACHINE_SOURCE_SAFETY_VERDICT = + 'source-safe-reading-shortest-path-state-machine-metadata'; + +export const V42_READING_SHORTEST_PATH_STEP_IDS = Object.freeze([ + 'request-read', + 'review-synthesized-need', + 'request-fit', + 'review-synthesized-asset-pack', + 'buy-asset-pack-settle', +]); + +export const V42_READING_SHORTEST_PATH_ROW_IDS = Object.freeze([ + 'state:five-step-shortest-path', + 'route:transaction-stage-persistence', + 'transition:accepted-need-before-finding-fits', + 'retry:restart-and-failure-repair', + 'ui:low-detail-proof-on-expand', + 'stream:rich-reading-pipeline-telemetry', + 'activity:history-and-workbench-readback', + 'tests:route-state-contracts', + 'spec:v42-gate3-closure', +]); + +const FORBIDDEN_PAYLOAD_CLASSES = Object.freeze([ + 'protected-source-payloads', + 'raw-protected-prompts', + 'raw-provider-responses', + 'unpaid-assetpack-source', + 'wallet-private-material', + 'settlement-private-payloads', + 'ledger-write-authority', + 'secret-values', +]); + +const SOURCE_ROOTS = Object.freeze({ + terminalUxState: 'uapi/app/terminal/terminal-enterprise-reading-ux-state.ts', + terminalWorkbench: 'uapi/app/terminal/TerminalDepositReadWorkbench.tsx', + terminalPageClient: 'uapi/app/terminal/TerminalPageClient.tsx', + terminalWorkbenchContract: 'uapi/app/terminal/terminal-deposit-read-workbench.ts', + terminalRouteQuery: 'uapi/app/terminal/terminal-transaction-query.ts', + terminalActivityHistory: 'uapi/app/terminal/terminal-activity-history.ts', + terminalHarnessClient: 'uapi/app/terminal/terminal-pipeline-harness-client.ts', + conversationHandoff: 'uapi/app/conversations/conversation-terminal-handoff.ts', + uxStateTest: 'uapi/tests/terminalEnterpriseReadingUxState.test.ts', + workbenchTest: 'uapi/tests/terminalDepositReadWorkbench.test.ts', + handoffTest: 'uapi/tests/conversationTerminalHandoff.test.tsx', + queryTest: 'uapi/tests/terminalTransactionQuery.test.ts', + streamHeaderTest: 'uapi/tests/pipelineExecutionLogHeader.test.tsx', + gateWorkflow: '.github/workflows/bitcode-gate-quality.yml', + v42Spec: 'BITCODE_SPEC_V42.md', + v42Delta: 'BITCODE_SPEC_V42_DELTA.md', + v42Notes: 'BITCODE_SPEC_V42_NOTES.md', + v42Parity: 'BITCODE_SPEC_V42_PARITY_MATRIX.md', + roadmap: 'SPECIFICATIONS_ROADMAP.md', + rootReadme: 'README.md', + terminalReadme: 'uapi/app/terminal/README.md', + protocolReadme: 'packages/protocol/README.md', +}); + +function digest(value) { + return crypto.createHash('sha256').update(value).digest('hex').slice(0, 24); +} + +function rowRoot(id) { + return `v42-reading-shortest-path-state-machine-row:${digest(id)}`; +} + +function readSource(repoRoot, sourcePath) { + const absolutePath = path.join(repoRoot, sourcePath); + return existsSync(absolutePath) ? readFileSync(absolutePath, 'utf8') : ''; +} + +function predicateResult(id, sourcePath, passed) { + return { id, sourcePath, passed: Boolean(passed) }; +} + +function row(input) { + return { + ...input, + rowRoot: rowRoot(input.rowId), + sourceSafetyClass: 'source_safe_reading_shortest_path_state_machine_metadata', + sourceSafeMetadataOnly: true, + lowDetailDefault: true, + expandableSourceSafeDetail: true, + protectedSourceVisible: false, + rawProtectedPromptVisible: false, + rawProviderResponseVisible: false, + unpaidAssetPackSourceVisible: false, + walletPrivateMaterialVisible: false, + settlementPrivatePayloadVisible: false, + ledgerAuthorityClaimed: false, + forbiddenPayloadClasses: [...FORBIDDEN_PAYLOAD_CLASSES], + }; +} + +export const V42_READING_SHORTEST_PATH_ROWS = Object.freeze([ + row({ + rowId: 'state:five-step-shortest-path', + purpose: + 'Preserve exactly five enterprise Reading steps from Read Request through Need review, Finding Fits, AssetPack preview, and settlement delivery.', + sourceRoots: [SOURCE_ROOTS.terminalUxState, SOURCE_ROOTS.terminalWorkbench], + emittedTypes: ['TerminalEnterpriseReadingUxState', 'TerminalEnterpriseReadingStepView'], + requiredEvidence: V42_READING_SHORTEST_PATH_STEP_IDS, + }), + row({ + rowId: 'route:transaction-stage-persistence', + purpose: + 'Bind Reading state to recoverable transaction ids and readingStage route state so refresh, restart, and route handoff preserve the active stage.', + sourceRoots: [ + SOURCE_ROOTS.terminalUxState, + SOURCE_ROOTS.terminalRouteQuery, + SOURCE_ROOTS.terminalPageClient, + SOURCE_ROOTS.terminalWorkbench, + ], + emittedTypes: ['TerminalEnterpriseReadingRouteState', 'TerminalConversationHandoffContext.readingStage'], + requiredEvidence: ['transactionIdRequiredForRecovery', 'readingStageQueryParam', 'activeStageHydratedFromRoute'], + }), + row({ + rowId: 'transition:accepted-need-before-finding-fits', + purpose: + 'Make accepted Need the hard transition before Finding Fits, preview, settlement, or delivery can proceed.', + sourceRoots: [SOURCE_ROOTS.terminalUxState, SOURCE_ROOTS.terminalWorkbench, SOURCE_ROOTS.conversationHandoff], + emittedTypes: ['acceptedNeedRequiredBeforeFindingFits', 'TerminalReadNeedState'], + requiredEvidence: ['accepted Need required', 'ReadNeedComprehensionSynthesis', 'ReadFitsFindingSynthesis'], + }), + row({ + rowId: 'retry:restart-and-failure-repair', + purpose: + 'Represent retry, restart, and failure repair posture as source-safe state metadata without exposing protected source, prompts, provider responses, wallet material, or settlement payloads.', + sourceRoots: [SOURCE_ROOTS.terminalUxState, SOURCE_ROOTS.uxStateTest, SOURCE_ROOTS.terminalWorkbench], + emittedTypes: ['TerminalEnterpriseReadingFailureKind', 'TerminalEnterpriseReadingRouteState.failureRepairActions'], + requiredEvidence: ['retryPreservesNeedLineage', 'restartRestoresActiveStage', 'failureRepairActions'], + }), + row({ + rowId: 'ui:low-detail-proof-on-expand', + purpose: + 'Keep the default Reading view guided and low-detail while details expand to source-safe proof roots, measurements, blockers, and visible field ids.', + sourceRoots: [SOURCE_ROOTS.terminalUxState, SOURCE_ROOTS.terminalWorkbench, SOURCE_ROOTS.terminalReadme], + emittedTypes: ['TerminalEnterpriseReadingUxState.disclosure'], + requiredEvidence: ['lowDetailDefault', 'expandableSourceSafeDetail', 'Source-safe detail'], + }), + row({ + rowId: 'stream:rich-reading-pipeline-telemetry', + purpose: + 'Keep Reading pipeline progress inspectable through rich execution stream rows for phase, PTRR step, ThricifiedGeneration, tool, prompt-template id, output schema, and parsed-result posture.', + sourceRoots: [SOURCE_ROOTS.terminalHarnessClient, SOURCE_ROOTS.streamHeaderTest, SOURCE_ROOTS.terminalWorkbench], + emittedTypes: ['TerminalReadFitsFindingSynthesisHarnessEvent', 'ExecutionLogItem'], + requiredEvidence: ['pipelinePhaseId', 'ptrrStepId', 'thricifiedGenerationId', 'promptTemplateId', 'outputSchema'], + }), + row({ + rowId: 'activity:history-and-workbench-readback', + purpose: + 'Project Reading state through activity history and workbench readback so transaction detail, proof roots, and compensation/settlement posture remain recoverable.', + sourceRoots: [SOURCE_ROOTS.terminalActivityHistory, SOURCE_ROOTS.terminalWorkbench, SOURCE_ROOTS.terminalWorkbenchContract], + emittedTypes: ['WorkspaceRun', 'TerminalDepositReadWorkbench'], + requiredEvidence: ['assetPackCompletion', 'TerminalDepositedSourceRevision', 'sourceRevision'], + }), + row({ + rowId: 'tests:route-state-contracts', + purpose: + 'Prove the state machine through route-state tests, component state tests, Conversation handoff tests, workbench tests, and stream-header tests.', + sourceRoots: [ + SOURCE_ROOTS.uxStateTest, + SOURCE_ROOTS.workbenchTest, + SOURCE_ROOTS.handoffTest, + SOURCE_ROOTS.queryTest, + SOURCE_ROOTS.streamHeaderTest, + SOURCE_ROOTS.gateWorkflow, + ], + emittedTypes: ['V42ReadingShortestPathStateMachineReport'], + requiredEvidence: ['terminal-enterprise-reading-ux-state', 'readingStage=request-fit', 'Browser proof Terminal cockpit'], + }), + row({ + rowId: 'spec:v42-gate3-closure', + purpose: + 'Bind V42 Gate 3 to SPEC, DELTA, NOTES, PARITY, roadmap, README, workflow, generated artifact, and checker closure.', + sourceRoots: [ + SOURCE_ROOTS.v42Spec, + SOURCE_ROOTS.v42Delta, + SOURCE_ROOTS.v42Notes, + SOURCE_ROOTS.v42Parity, + SOURCE_ROOTS.roadmap, + SOURCE_ROOTS.rootReadme, + SOURCE_ROOTS.protocolReadme, + ], + emittedTypes: ['V42ReadingShortestPathStateMachineReport'], + requiredEvidence: ['V42 Gate 3', 'reading shortest path state machine', 'check:v42-gate3'], + }), +]); + +function buildPredicateResults(repoRoot) { + const uxState = readSource(repoRoot, SOURCE_ROOTS.terminalUxState); + const pageClient = readSource(repoRoot, SOURCE_ROOTS.terminalPageClient); + const workbench = readSource(repoRoot, SOURCE_ROOTS.terminalWorkbench); + const workbenchContract = readSource(repoRoot, SOURCE_ROOTS.terminalWorkbenchContract); + const query = readSource(repoRoot, SOURCE_ROOTS.terminalRouteQuery); + const activity = readSource(repoRoot, SOURCE_ROOTS.terminalActivityHistory); + const harness = readSource(repoRoot, SOURCE_ROOTS.terminalHarnessClient); + const handoff = readSource(repoRoot, SOURCE_ROOTS.conversationHandoff); + const uxStateTest = readSource(repoRoot, SOURCE_ROOTS.uxStateTest); + const workbenchTest = readSource(repoRoot, SOURCE_ROOTS.workbenchTest); + const handoffTest = readSource(repoRoot, SOURCE_ROOTS.handoffTest); + const queryTest = readSource(repoRoot, SOURCE_ROOTS.queryTest); + const streamHeaderTest = readSource(repoRoot, SOURCE_ROOTS.streamHeaderTest); + const gateWorkflow = readSource(repoRoot, SOURCE_ROOTS.gateWorkflow); + const spec = readSource(repoRoot, SOURCE_ROOTS.v42Spec); + const delta = readSource(repoRoot, SOURCE_ROOTS.v42Delta); + const notes = readSource(repoRoot, SOURCE_ROOTS.v42Notes); + const parity = readSource(repoRoot, SOURCE_ROOTS.v42Parity); + const roadmap = readSource(repoRoot, SOURCE_ROOTS.roadmap); + const rootReadme = readSource(repoRoot, SOURCE_ROOTS.rootReadme); + const terminalReadme = readSource(repoRoot, SOURCE_ROOTS.terminalReadme); + const protocolReadme = readSource(repoRoot, SOURCE_ROOTS.protocolReadme); + + return [ + predicateResult('ux-state-keeps-five-step-path', SOURCE_ROOTS.terminalUxState, V42_READING_SHORTEST_PATH_STEP_IDS.every((id) => uxState.includes(id)) && uxState.includes('stageCount: 5')), + predicateResult('ux-state-defines-route-state', SOURCE_ROOTS.terminalUxState, uxState.includes('TerminalEnterpriseReadingRouteState') && uxState.includes('transactionIdRequiredForRecovery') && uxState.includes("readingStageQueryParam: 'readingStage'")), + predicateResult('ux-state-defines-retry-failure-source-safety', SOURCE_ROOTS.terminalUxState, uxState.includes('TerminalEnterpriseReadingFailureKind') && uxState.includes('retryPreservesNeedLineage') && uxState.includes('failureRepairActions') && uxState.includes('failureStateSourceSafe')), + predicateResult('ux-state-forbids-protected-payloads', SOURCE_ROOTS.terminalUxState, uxState.includes('protected_source_payload') && uxState.includes('raw_protected_prompt') && uxState.includes('raw_provider_response') && uxState.includes('unpaid_assetpack_source') && uxState.includes('wallet_private_material') && uxState.includes('settlement_private_payload')), + predicateResult('terminal-page-passes-reading-stage', SOURCE_ROOTS.terminalPageClient, pageClient.includes('routeReadingStage={conversationHandoffContext.readingStage}')), + predicateResult('workbench-projects-route-state', SOURCE_ROOTS.terminalWorkbench, workbench.includes('transactionId: recordedAdmittedReadActivityId') && workbench.includes('routeReadingStage') && workbench.includes('data-reading-transaction-present') && workbench.includes('data-reading-failure-kind')), + predicateResult('workbench-keeps-low-detail-expandable-cards', SOURCE_ROOTS.terminalWorkbench, workbench.includes('terminal-enterprise-reading-step-${stage.id}') && workbench.includes('data-reading-step-state') && workbench.includes('Source-safe detail')), + predicateResult('workbench-contract-reexports-stage-ids', SOURCE_ROOTS.terminalWorkbenchContract, workbenchContract.includes('TERMINAL_ENTERPRISE_READING_STEPS') && workbenchContract.includes('TerminalEnterpriseReadingStepId')), + predicateResult('terminal-query-reads-reading-stage', SOURCE_ROOTS.terminalRouteQuery, query.includes('readingStage') && query.includes('TERMINAL_ENTERPRISE_READING_STAGE_VALUES') && query.includes('readTerminalTransactionId')), + predicateResult('activity-history-keeps-reading-readback', SOURCE_ROOTS.terminalActivityHistory, activity.includes('assetPackCompletion') && activity.includes('sourceRevision')), + predicateResult('harness-projects-rich-reading-telemetry', SOURCE_ROOTS.terminalHarnessClient, harness.includes('ReadFitsFindingSynthesis') && harness.includes('ptrrStepId') && harness.includes('thricifiedGenerationId') && harness.includes('promptTemplateId') && harness.includes('outputSchema')), + predicateResult('conversation-handoff-preserves-reading-stage', SOURCE_ROOTS.conversationHandoff, handoff.includes('inferConversationTerminalReadingStage') && handoff.includes('terminalEnterpriseReadingStage') && handoff.includes("params.set('readingStage'")), + predicateResult('ux-state-tests-cover-route-retry-failure', SOURCE_ROOTS.uxStateTest, uxStateTest.includes('hydrates later route stages') && uxStateTest.includes('repair-settlement-readback')), + predicateResult('workbench-tests-cover-five-stage-labels', SOURCE_ROOTS.workbenchTest, workbenchTest.includes('3. Request Finding Fits') && workbenchTest.includes('buy-asset-pack-settle')), + predicateResult('handoff-tests-cover-reading-stage-route', SOURCE_ROOTS.handoffTest, handoffTest.includes('readingStage=request-fit') && handoffTest.includes('terminalEnterpriseReadingStage')), + predicateResult('query-tests-cover-reading-stage-route', SOURCE_ROOTS.queryTest, queryTest.includes('reads source-safe enterprise Reading stage') && queryTest.includes('request-fit')), + predicateResult('stream-tests-cover-rich-header', SOURCE_ROOTS.streamHeaderTest, streamHeaderTest.includes('ReadFitsFindingSynthesis') && streamHeaderTest.includes('outputSchema') && streamHeaderTest.includes('prompt_template_id_only')), + predicateResult('workflow-wires-gate3-check', SOURCE_ROOTS.gateWorkflow, gateWorkflow.includes('check-v42-gate3-reading-shortest-path-state-machine.mjs')), + predicateResult('v42-spec-gate3-expanded', SOURCE_ROOTS.v42Spec, spec.includes('V42 Gate 3') && spec.includes('reading shortest path state machine')), + predicateResult('v42-delta-gate3-expanded', SOURCE_ROOTS.v42Delta, delta.includes('Gate 3') && delta.includes('route-owned Reading state')), + predicateResult('v42-notes-gate3-expanded', SOURCE_ROOTS.v42Notes, notes.includes('Gate 3') && notes.includes('transaction id')), + predicateResult('v42-parity-gate3-implemented', SOURCE_ROOTS.v42Parity, parity.includes('Reading state machine') && parity.includes('implemented')), + predicateResult( + 'roadmap-records-gate3-closure', + SOURCE_ROOTS.roadmap, + roadmap.includes('Current working gate: V42 Gate') && roadmap.includes('V42 Gate 3 closure anchor'), + ), + predicateResult('readmes-document-gate3', SOURCE_ROOTS.rootReadme, rootReadme.includes('V42 Gate 3') && terminalReadme.includes('TerminalEnterpriseReadingUxState') && protocolReadme.includes('V42 Reading shortest path')), + ]; +} + +export function buildV42ReadingShortestPathStateMachine(options = {}) { + const repoRoot = options.repoRoot || DEFAULT_REPO_ROOT; + const predicateResults = buildPredicateResults(repoRoot); + const failedPredicateIds = predicateResults + .filter((predicate) => !predicate.passed) + .map((predicate) => predicate.id); + const rowRoots = V42_READING_SHORTEST_PATH_ROWS.map((item) => item.rowRoot); + const artifactRoot = `v42-reading-shortest-path-state-machine:${digest(JSON.stringify({ + rowRoots, + failedPredicateIds, + }))}`; + + return { + artifactId: 'v42-reading-shortest-path-state-machine', + schemaId: V42_READING_SHORTEST_PATH_STATE_MACHINE_SCHEMA_ID, + version: V42_READING_SHORTEST_PATH_STATE_MACHINE_VERSION, + currentTarget: V42_READING_SHORTEST_PATH_STATE_MACHINE_CURRENT_TARGET, + sourceSafetyVerdict: V42_READING_SHORTEST_PATH_STATE_MACHINE_SOURCE_SAFETY_VERDICT, + generatedAt: 'deterministic', + artifactRoot, + passed: failedPredicateIds.length === 0, + rows: V42_READING_SHORTEST_PATH_ROWS, + rowIds: [...V42_READING_SHORTEST_PATH_ROW_IDS], + stepIds: [...V42_READING_SHORTEST_PATH_STEP_IDS], + predicateResults, + coverage: { + rowCount: V42_READING_SHORTEST_PATH_ROWS.length, + stepCount: V42_READING_SHORTEST_PATH_STEP_IDS.length, + acceptedUserPath: [ + 'request-read', + 'review-synthesized-need', + 'request-finding-fits', + 'review-source-safe-assetpack-preview', + 'buy-settle-and-deliver-assetpack', + ], + requiredPredicateCount: predicateResults.length, + passedPredicateCount: predicateResults.length - failedPredicateIds.length, + failedPredicateIds, + routePersistenceCovered: true, + transactionIdRecoveryCovered: true, + restartRetryFailureCovered: true, + acceptedNeedGateCovered: true, + streamLogIntegrationCovered: true, + componentRouteTestsCovered: true, + sourceSafeMetadataOnly: true, + lowDetailDefault: true, + expandableSourceSafeDetail: true, + protectedSourceVisible: false, + rawProtectedPromptVisible: false, + rawProviderResponseVisible: false, + unpaidAssetPackSourceVisible: false, + walletPrivateMaterialVisible: false, + settlementPrivatePayloadVisible: false, + ledgerAuthorityClaimed: false, + legacySourceRoots: Object.values(SOURCE_ROOTS).some((sourcePath) => sourcePath.includes('_legacy/')), + }, + sourceRoots: SOURCE_ROOTS, + }; +} diff --git a/packages/protocol/src/index.d.ts b/packages/protocol/src/index.d.ts index d163ccbe..7bef5cb2 100644 --- a/packages/protocol/src/index.d.ts +++ b/packages/protocol/src/index.d.ts @@ -520,6 +520,15 @@ export const V42_DEPOSITING_SHORTEST_PATH_SOURCE_SAFETY_VERDICT: string; export const V42_DEPOSITING_SHORTEST_PATH_ROW_IDS: readonly string[]; export const V42_DEPOSITING_SHORTEST_PATH_ROWS: readonly Record[]; export function buildV42DepositingShortestPath(input?: Record): BitcodeProtocolReport; +export const V42_READING_SHORTEST_PATH_STATE_MACHINE_ARTIFACT_PATH: string; +export const V42_READING_SHORTEST_PATH_STATE_MACHINE_CURRENT_TARGET: string; +export const V42_READING_SHORTEST_PATH_SCHEMA_ID: string; +export const V42_READING_SHORTEST_PATH_STATE_MACHINE_VERSION: string; +export const V42_READING_SHORTEST_PATH_STATE_MACHINE_SOURCE_SAFETY_VERDICT: string; +export const V42_READING_SHORTEST_PATH_STEP_IDS: readonly string[]; +export const V42_READING_SHORTEST_PATH_ROW_IDS: readonly string[]; +export const V42_READING_SHORTEST_PATH_ROWS: readonly Record[]; +export function buildV42ReadingShortestPathStateMachine(input?: Record): BitcodeProtocolReport; export const EXCHANGE_INTENT_ORDER_CONTRACTS_ARTIFACT_PATH: string; export const EXCHANGE_INTENT_ORDER_CONTRACTS_CURRENT_TARGET: string; export const EXCHANGE_INTENT_ORDER_CONTRACTS_SCHEMA_ID: string; diff --git a/packages/protocol/src/index.js b/packages/protocol/src/index.js index 307cc3f8..7f07b1c9 100644 --- a/packages/protocol/src/index.js +++ b/packages/protocol/src/index.js @@ -580,6 +580,17 @@ export { V42_DEPOSITING_SHORTEST_PATH_VERSION, buildV42DepositingShortestPath } from './canonical/v42-depositing-shortest-path.js'; +export { + V42_READING_SHORTEST_PATH_ROWS, + V42_READING_SHORTEST_PATH_ROW_IDS, + V42_READING_SHORTEST_PATH_SCHEMA_ID, + V42_READING_SHORTEST_PATH_STATE_MACHINE_ARTIFACT_PATH, + V42_READING_SHORTEST_PATH_STATE_MACHINE_CURRENT_TARGET, + V42_READING_SHORTEST_PATH_STATE_MACHINE_SOURCE_SAFETY_VERDICT, + V42_READING_SHORTEST_PATH_STATE_MACHINE_VERSION, + V42_READING_SHORTEST_PATH_STEP_IDS, + buildV42ReadingShortestPathStateMachine +} from './canonical/v42-reading-shortest-path-state-machine.js'; export { EXCHANGE_INTENT_ACTION_KINDS, EXCHANGE_INTENT_ORDER_CONTRACTS_ARTIFACT_PATH, diff --git a/packages/protocol/test/v42-reading-shortest-path-state-machine.test.js b/packages/protocol/test/v42-reading-shortest-path-state-machine.test.js new file mode 100644 index 00000000..db12e040 --- /dev/null +++ b/packages/protocol/test/v42-reading-shortest-path-state-machine.test.js @@ -0,0 +1,77 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + V42_READING_SHORTEST_PATH_ROWS, + V42_READING_SHORTEST_PATH_ROW_IDS, + V42_READING_SHORTEST_PATH_SCHEMA_ID, + V42_READING_SHORTEST_PATH_STATE_MACHINE_ARTIFACT_PATH, + V42_READING_SHORTEST_PATH_STATE_MACHINE_SOURCE_SAFETY_VERDICT, + V42_READING_SHORTEST_PATH_STEP_IDS, + buildV42ReadingShortestPathStateMachine, +} from '../src/canonical/v42-reading-shortest-path-state-machine.js'; + +test('V42 Reading shortest path state machine binds five route-recoverable steps', () => { + const report = buildV42ReadingShortestPathStateMachine(); + + assert.equal( + V42_READING_SHORTEST_PATH_STATE_MACHINE_ARTIFACT_PATH, + '.bitcode/v42-reading-shortest-path-state-machine.json', + ); + assert.equal(report.artifactId, 'v42-reading-shortest-path-state-machine'); + assert.equal(report.schemaId, V42_READING_SHORTEST_PATH_SCHEMA_ID); + assert.equal(report.version, 'V42'); + assert.equal(report.currentTarget, 'V41'); + assert.equal(report.sourceSafetyVerdict, V42_READING_SHORTEST_PATH_STATE_MACHINE_SOURCE_SAFETY_VERDICT); + assert.equal(report.passed, true); + assert.deepEqual(report.rowIds, [...V42_READING_SHORTEST_PATH_ROW_IDS]); + assert.deepEqual(report.stepIds, [...V42_READING_SHORTEST_PATH_STEP_IDS]); + assert.equal(report.rows.length, V42_READING_SHORTEST_PATH_ROWS.length); + assert.equal(report.coverage.rowCount, 9); + assert.equal(report.coverage.stepCount, 5); + assert.deepEqual(report.coverage.acceptedUserPath, [ + 'request-read', + 'review-synthesized-need', + 'request-finding-fits', + 'review-source-safe-assetpack-preview', + 'buy-settle-and-deliver-assetpack', + ]); + assert.equal(report.coverage.routePersistenceCovered, true); + assert.equal(report.coverage.transactionIdRecoveryCovered, true); + assert.equal(report.coverage.restartRetryFailureCovered, true); + assert.equal(report.coverage.acceptedNeedGateCovered, true); + assert.equal(report.coverage.streamLogIntegrationCovered, true); + assert.equal(report.coverage.componentRouteTestsCovered, true); + assert.equal(report.coverage.sourceSafeMetadataOnly, true); + assert.equal(report.coverage.lowDetailDefault, true); + assert.equal(report.coverage.expandableSourceSafeDetail, true); + assert.equal(report.coverage.protectedSourceVisible, false); + assert.equal(report.coverage.rawProtectedPromptVisible, false); + assert.equal(report.coverage.rawProviderResponseVisible, false); + assert.equal(report.coverage.unpaidAssetPackSourceVisible, false); + assert.equal(report.coverage.walletPrivateMaterialVisible, false); + assert.equal(report.coverage.settlementPrivatePayloadVisible, false); + assert.equal(report.coverage.ledgerAuthorityClaimed, false); + assert.equal(report.coverage.legacySourceRoots, false); + assert.deepEqual(report.coverage.failedPredicateIds, []); + assert.ok(report.artifactRoot.startsWith('v42-reading-shortest-path-state-machine:')); +}); + +test('V42 Reading shortest path rows remain expandable source-safe metadata', () => { + for (const row of V42_READING_SHORTEST_PATH_ROWS) { + assert.ok(row.rowRoot.startsWith('v42-reading-shortest-path-state-machine-row:')); + assert.equal(row.sourceSafetyClass, 'source_safe_reading_shortest_path_state_machine_metadata'); + assert.equal(row.sourceSafeMetadataOnly, true); + assert.equal(row.lowDetailDefault, true); + assert.equal(row.expandableSourceSafeDetail, true); + assert.equal(row.protectedSourceVisible, false); + assert.equal(row.rawProtectedPromptVisible, false); + assert.equal(row.rawProviderResponseVisible, false); + assert.equal(row.unpaidAssetPackSourceVisible, false); + assert.equal(row.walletPrivateMaterialVisible, false); + assert.equal(row.settlementPrivatePayloadVisible, false); + assert.equal(row.ledgerAuthorityClaimed, false); + assert.ok(row.forbiddenPayloadClasses.includes('protected-source-payloads')); + assert.ok(row.forbiddenPayloadClasses.includes('unpaid-assetpack-source')); + } +}); diff --git a/scripts/check-v42-gate3-reading-shortest-path-state-machine.mjs b/scripts/check-v42-gate3-reading-shortest-path-state-machine.mjs new file mode 100644 index 00000000..0376ffc5 --- /dev/null +++ b/scripts/check-v42-gate3-reading-shortest-path-state-machine.mjs @@ -0,0 +1,240 @@ +#!/usr/bin/env node + +import { execFileSync } from 'node:child_process'; +import { existsSync, readFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const defaultRepoRoot = path.resolve(__dirname, '..'); +const ARTIFACT_PATH = '.bitcode/v42-reading-shortest-path-state-machine.json'; + +const SECRET_MARKERS = [ + `${['sk', 'proj'].join('-')}-`, + `${['sb', 'secret'].join('_')}__`, + ['service', 'role'].join('_'), + Buffer.from('{"alg":"HS256","typ":"JWT"}').toString('base64url').slice(0, 18), + ['OPENAI', 'API', 'KEY'].join('_'), + ['SUPABASE', 'SERVICE', 'ROLE'].join('_'), + ['VERCEL', 'TOKEN'].join('_'), + ['VERCEL', 'OIDC', 'TOKEN'].join('_'), + ['PRIVATE', 'KEY'].join('_'), +]; + +function read(root, relativePath) { + return readFileSync(path.join(root, relativePath), 'utf8'); +} + +function fileExists(root, relativePath) { + return existsSync(path.join(root, relativePath)); +} + +function git(root, args) { + return execFileSync('git', args, { cwd: root, encoding: 'utf8' }).trim(); +} + +function run(root, command, args) { + return execFileSync(command, args, { + cwd: root, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + }).trim(); +} + +function assertCheck(failures, condition, message) { + if (!condition) failures.push(message); +} + +function parseArgs(argv) { + const args = { + skipBranchCheck: false, + skipUapiTests: false, + repoRoot: defaultRepoRoot, + }; + + for (let index = 0; index < argv.length; index += 1) { + const arg = argv[index]; + if (arg === '--skip-branch-check') args.skipBranchCheck = true; + else if (arg === '--skip-uapi-tests') args.skipUapiTests = true; + else if (arg === '--repo-root') args.repoRoot = path.resolve(argv[++index]); + else if (arg === '--help' || arg === '-h') args.help = true; + else throw new Error(`Unknown argument ${arg}`); + } + + return args; +} + +function printHelp() { + process.stdout.write( + [ + 'Usage: node scripts/check-v42-gate3-reading-shortest-path-state-machine.mjs [--skip-branch-check] [--skip-uapi-tests] [--repo-root ]', + '', + 'Checks V42 Gate 3 Reading shortest path state machine, route recovery, retry/failure posture, source-safe disclosure, workflow wiring, docs, tests, and generated artifact.', + ].join('\n'), + ); + process.stdout.write('\n'); +} + +function main() { + const args = parseArgs(process.argv.slice(2)); + if (args.help) { + printHelp(); + return; + } + + const root = args.repoRoot; + const failures = []; + const pointer = read(root, 'BITCODE_SPEC.txt').trim(); + + assertCheck( + failures, + pointer === 'V41', + `BITCODE_SPEC.txt must remain V41 during V42 gate work. Observed ${pointer || 'empty'}.`, + ); + + if (!args.skipBranchCheck) { + const branch = git(root, ['branch', '--show-current']); + assertCheck( + failures, + branch === 'version/v42' || /^v42\/gate-(?:3|[4-9]|10)-[a-z0-9][a-z0-9-]*$/u.test(branch), + `V42 Gate 3+ work must occur on version/v42 or v42/gate-3..10-* branches. Observed ${branch || 'detached HEAD'}.`, + ); + } + + const requiredFiles = [ + ARTIFACT_PATH, + 'uapi/app/terminal/TerminalPageClient.tsx', + 'uapi/app/terminal/terminal-enterprise-reading-ux-state.ts', + 'uapi/app/terminal/TerminalDepositReadWorkbench.tsx', + 'uapi/app/terminal/terminal-deposit-read-workbench.ts', + 'uapi/app/terminal/terminal-pipeline-harness-client.ts', + 'uapi/app/conversations/conversation-terminal-handoff.ts', + 'uapi/app/terminal/terminal-transaction-query.ts', + 'uapi/app/terminal/terminal-activity-history.ts', + 'uapi/tests/terminalEnterpriseReadingUxState.test.ts', + 'uapi/tests/terminalDepositReadWorkbench.test.ts', + 'uapi/tests/conversationTerminalHandoff.test.tsx', + 'uapi/tests/terminalTransactionQuery.test.ts', + 'uapi/tests/terminalPipelineHarnessClient.test.ts', + 'uapi/tests/pipelineExecutionLogHeader.test.tsx', + 'packages/protocol/src/canonical/v42-reading-shortest-path-state-machine.js', + 'packages/protocol/test/v42-reading-shortest-path-state-machine.test.js', + 'scripts/generate-v42-reading-shortest-path-state-machine.mjs', + 'scripts/check-v42-gate3-reading-shortest-path-state-machine.mjs', + 'BITCODE_SPEC_V42.md', + 'BITCODE_SPEC_V42_DELTA.md', + 'BITCODE_SPEC_V42_NOTES.md', + 'BITCODE_SPEC_V42_PARITY_MATRIX.md', + 'SPECIFICATIONS_ROADMAP.md', + 'README.md', + 'uapi/app/terminal/README.md', + 'packages/protocol/README.md', + 'package.json', + '.github/workflows/bitcode-gate-quality.yml', + '.github/workflows/bitcode-canon-quality.yml', + ]; + + for (const relativePath of requiredFiles) { + assertCheck(failures, fileExists(root, relativePath), `Missing V42 Gate 3 file: ${relativePath}`); + } + + if (failures.length === 0) { + try { + run(root, 'node', ['scripts/generate-v42-reading-shortest-path-state-machine.mjs', '--check']); + } catch (error) { + failures.push(`V42 Reading shortest path state machine artifact check failed: ${error.stderr || error.message}`); + } + } + + if (failures.length === 0) { + try { + run(root, 'node', [ + '--test', + '--test-force-exit', + 'packages/protocol/test/v42-reading-shortest-path-state-machine.test.js', + ]); + } catch (error) { + failures.push(`V42 Reading shortest path state machine protocol test failed: ${error.stderr || error.message}`); + } + } + + if (failures.length === 0 && !args.skipUapiTests) { + try { + run(root, 'pnpm', [ + '--dir', + 'uapi', + 'exec', + 'jest', + '--runTestsByPath', + 'tests/terminalEnterpriseReadingUxState.test.ts', + 'tests/terminalDepositReadWorkbench.test.ts', + 'tests/conversationTerminalHandoff.test.tsx', + 'tests/terminalTransactionQuery.test.ts', + 'tests/terminalPipelineHarnessClient.test.ts', + 'tests/pipelineExecutionLogHeader.test.tsx', + '--runInBand', + ]); + } catch (error) { + failures.push(`V42 Reading shortest path state machine uapi tests failed: ${error.stderr || error.message}`); + } + } + + const serializedArtifact = fileExists(root, ARTIFACT_PATH) ? read(root, ARTIFACT_PATH) : ''; + for (const marker of SECRET_MARKERS) { + assertCheck(failures, !serializedArtifact.includes(marker), `V42 Gate 3 artifact must not contain secret marker ${marker}.`); + } + + const artifact = serializedArtifact ? JSON.parse(serializedArtifact) : null; + if (artifact) { + assertCheck(failures, artifact.artifactId === 'v42-reading-shortest-path-state-machine', 'Gate 3 artifactId must match.'); + assertCheck( + failures, + artifact.schemaId === 'bitcode.v42.readingShortestPathStateMachine.v1', + 'Gate 3 schemaId must match.', + ); + assertCheck(failures, artifact.version === 'V42' && artifact.currentTarget === 'V41', 'Gate 3 artifact must bind V42 over active V41.'); + assertCheck(failures, artifact.passed === true, 'Gate 3 artifact must pass.'); + assertCheck( + failures, + artifact.sourceSafetyVerdict === 'source-safe-reading-shortest-path-state-machine-metadata', + 'Gate 3 artifact must declare source-safe Reading state-machine metadata.', + ); + assertCheck(failures, artifact.coverage.rowCount === 9, 'Gate 3 must cover nine Reading state-machine rows.'); + assertCheck(failures, artifact.coverage.stepCount === 5, 'Gate 3 must cover five Reading steps.'); + assertCheck(failures, artifact.coverage.routePersistenceCovered === true, 'Gate 3 must cover route persistence.'); + assertCheck(failures, artifact.coverage.transactionIdRecoveryCovered === true, 'Gate 3 must cover transaction id recovery.'); + assertCheck(failures, artifact.coverage.restartRetryFailureCovered === true, 'Gate 3 must cover restart, retry, and failure repair.'); + assertCheck(failures, artifact.coverage.acceptedNeedGateCovered === true, 'Gate 3 must cover accepted-Need gating.'); + assertCheck(failures, artifact.coverage.streamLogIntegrationCovered === true, 'Gate 3 must cover stream log integration.'); + assertCheck(failures, artifact.coverage.componentRouteTestsCovered === true, 'Gate 3 must cover component and route tests.'); + assertCheck(failures, artifact.coverage.sourceSafeMetadataOnly === true, 'Gate 3 must remain source-safe metadata only.'); + assertCheck(failures, artifact.coverage.lowDetailDefault === true, 'Gate 3 must preserve low-detail defaults.'); + assertCheck(failures, artifact.coverage.expandableSourceSafeDetail === true, 'Gate 3 must preserve expandable source-safe detail.'); + assertCheck(failures, artifact.coverage.protectedSourceVisible === false, 'Gate 3 artifact must not expose protected source.'); + assertCheck(failures, artifact.coverage.rawProtectedPromptVisible === false, 'Gate 3 artifact must not expose protected prompts.'); + assertCheck(failures, artifact.coverage.rawProviderResponseVisible === false, 'Gate 3 artifact must not expose raw provider responses.'); + assertCheck(failures, artifact.coverage.unpaidAssetPackSourceVisible === false, 'Gate 3 artifact must not expose unpaid AssetPack source.'); + assertCheck(failures, artifact.coverage.walletPrivateMaterialVisible === false, 'Gate 3 artifact must not expose wallet private material.'); + assertCheck(failures, artifact.coverage.ledgerAuthorityClaimed === false, 'Gate 3 artifact must not claim ledger authority.'); + assertCheck(failures, artifact.coverage.legacySourceRoots === false, 'Gate 3 must not rely on legacy source roots.'); + assertCheck(failures, Array.isArray(artifact.coverage.failedPredicateIds) && artifact.coverage.failedPredicateIds.length === 0, 'Gate 3 predicates must all pass.'); + } + + const spec = read(root, 'BITCODE_SPEC_V42.md'); + const parity = read(root, 'BITCODE_SPEC_V42_PARITY_MATRIX.md'); + const terminalReadme = read(root, 'uapi/app/terminal/README.md'); + assertCheck(failures, spec.includes('V42 Gate 3') && spec.includes('reading shortest path state machine'), 'V42 spec must expand Gate 3 state machine.'); + assertCheck(failures, parity.includes('Reading state machine') && parity.includes('implemented'), 'V42 parity matrix must mark Reading state machine implemented.'); + assertCheck(failures, terminalReadme.includes('V42 Gate 3') && terminalReadme.includes('TerminalEnterpriseReadingRouteState'), 'Terminal README must document Gate 3 route state.'); + + if (failures.length > 0) { + process.stderr.write(`V42 Gate 3 Reading shortest path state machine check failed:\n- ${failures.join('\n- ')}\n`); + process.exitCode = 1; + return; + } + + process.stdout.write(`V42 Gate 3 Reading shortest path state machine ok artifact=${artifact.artifactRoot}\n`); +} + +main(); diff --git a/scripts/generate-v42-reading-shortest-path-state-machine.mjs b/scripts/generate-v42-reading-shortest-path-state-machine.mjs new file mode 100644 index 00000000..615153ae --- /dev/null +++ b/scripts/generate-v42-reading-shortest-path-state-machine.mjs @@ -0,0 +1,31 @@ +#!/usr/bin/env node + +import { readFileSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { + V42_READING_SHORTEST_PATH_STATE_MACHINE_ARTIFACT_PATH, + buildV42ReadingShortestPathStateMachine, +} from '../packages/protocol/src/canonical/v42-reading-shortest-path-state-machine.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const repoRoot = path.resolve(__dirname, '..'); + +const check = process.argv.includes('--check'); +const artifact = buildV42ReadingShortestPathStateMachine({ repoRoot }); +const serialized = `${JSON.stringify(artifact, null, 2)}\n`; +const artifactPath = path.join(repoRoot, V42_READING_SHORTEST_PATH_STATE_MACHINE_ARTIFACT_PATH); + +if (check) { + const current = readFileSync(artifactPath, 'utf8'); + if (current !== serialized) { + process.stderr.write( + `${V42_READING_SHORTEST_PATH_STATE_MACHINE_ARTIFACT_PATH} is stale. Run pnpm run generate:v42-reading-shortest-path-state-machine.\n`, + ); + process.exitCode = 1; + } +} else { + writeFileSync(artifactPath, serialized); + process.stdout.write(`wrote ${V42_READING_SHORTEST_PATH_STATE_MACHINE_ARTIFACT_PATH}\n`); +} diff --git a/uapi/app/terminal/README.md b/uapi/app/terminal/README.md index 7867c6b7..176bc0a0 100644 --- a/uapi/app/terminal/README.md +++ b/uapi/app/terminal/README.md @@ -105,6 +105,16 @@ parses it only as operator posture. The source-safe generated artifact is `.bitcode/v39-enterprise-reading-ux-state.json`, checked by `pnpm run check:v39-gate3`. +V42 Gate 3 extends that state into the current Reading shortest path recovery +contract. `TerminalEnterpriseReadingRouteState` binds transaction id presence, +the `readingStage` query parameter, active-stage hydration, retry and restart +posture, source-safe failure kind, and repair actions. Terminal may restore a +later stage from route state after refresh or handoff, but blockers still hold: +accepted Need gates Finding Fits, source-safe AssetPack preview gates +settlement, and settlement readback gates delivery. The source-safe proof +artifact is `.bitcode/v42-reading-shortest-path-state-machine.json`, checked by +`pnpm run check:v42-gate3`. + ## Live staging-testnet QA Terminal Deposit/Read QA starts only after Wallet and Externals prerequisites are diff --git a/uapi/app/terminal/TerminalDepositReadWorkbench.tsx b/uapi/app/terminal/TerminalDepositReadWorkbench.tsx index 6d0e756b..4d4e1453 100644 --- a/uapi/app/terminal/TerminalDepositReadWorkbench.tsx +++ b/uapi/app/terminal/TerminalDepositReadWorkbench.tsx @@ -108,6 +108,7 @@ interface TerminalDepositReadWorkbenchProps { repositoryContext?: TerminalRepositoryContextState | null; depositedSourceRevision?: TerminalDepositedSourceRevision | null; admittedReadActivityId?: string | null; + routeReadingStage?: TerminalEnterpriseReadingStepId | null; onRecordActivity?: (draft: TerminalActivityRecordDraft) => Promise; onHarnessCompleted?: () => Promise | unknown; showDemonstrationWorkbench?: boolean; @@ -117,6 +118,7 @@ export default function TerminalDepositReadWorkbench({ repositoryContext = null, depositedSourceRevision = null, admittedReadActivityId = null, + routeReadingStage = null, onRecordActivity, onHarnessCompleted, showDemonstrationWorkbench = true, @@ -304,6 +306,8 @@ export default function TerminalDepositReadWorkbench({ const enterpriseReadingState = useMemo( () => buildTerminalEnterpriseReadingUxState({ + transactionId: recordedAdmittedReadActivityId || harnessReadActivityId || admittedReadActivityId || null, + routeReadingStage, hasRepositorySource: Boolean(workbench?.sourceRevision), hasReadMeasurement: readFitsFindingProgress !== 'draft' || Boolean(harnessReadActivityId), hasSynthesizedNeed: Boolean(readNeed), @@ -312,18 +316,24 @@ export default function TerminalDepositReadWorkbench({ hasSourceSafePreview: Boolean(sourceSafePreview), hasSettlementReadback: settledReadback, hasDeliveryReadback: pullRequestDelivered, + retryRequested: readNeedSynthesisCount > 1 || harnessState === 'failed', + failureKind: harnessState === 'failed' ? 'fits_finding_failed' : null, sourceSafePreviewBlocked: Boolean(sourceSafePreview && !disclosureSourceSafe), disclosureLeakageDetected: disclosureLeakage?.protectedSourceDetected === true, }), [ acceptedReadNeed, + admittedReadActivityId, disclosureLeakage?.protectedSourceDetected, disclosureSourceSafe, harnessReadActivityId, harnessState, + readNeedSynthesisCount, pullRequestDelivered, readFitsFindingProgress, + recordedAdmittedReadActivityId, readNeed, + routeReadingStage, settledReadback, sourceSafePreview, workbench?.sourceRevision, @@ -711,7 +721,12 @@ export default function TerminalDepositReadWorkbench({ /> -
+

staged reading

diff --git a/uapi/app/terminal/TerminalPageClient.tsx b/uapi/app/terminal/TerminalPageClient.tsx index 33d0e3ca..f33d3aa2 100644 --- a/uapi/app/terminal/TerminalPageClient.tsx +++ b/uapi/app/terminal/TerminalPageClient.tsx @@ -691,6 +691,7 @@ export default function TerminalPageClient() { repositoryContext={repositoryContext} depositedSourceRevision={depositedSourceRevision} admittedReadActivityId={admittedReadActivityId} + routeReadingStage={conversationHandoffContext.readingStage} onRecordActivity={handleRecordActivity} onHarnessCompleted={refreshLiveRuns} showDemonstrationWorkbench={showDemonstrationSurfaces} diff --git a/uapi/app/terminal/terminal-enterprise-reading-ux-state.ts b/uapi/app/terminal/terminal-enterprise-reading-ux-state.ts index 54911805..305a345d 100644 --- a/uapi/app/terminal/terminal-enterprise-reading-ux-state.ts +++ b/uapi/app/terminal/terminal-enterprise-reading-ux-state.ts @@ -7,6 +7,16 @@ export type TerminalEnterpriseReadingStepId = export type TerminalEnterpriseReadingStepState = 'complete' | 'current' | 'blocked'; +export type TerminalEnterpriseReadingFailureKind = + | 'none' + | 'read_request_invalid' + | 'need_review_required' + | 'fits_finding_failed' + | 'asset_pack_preview_blocked' + | 'settlement_blocked' + | 'delivery_blocked' + | 'source_safety_blocked'; + export type TerminalEnterpriseReadingSourceSafeField = | 'read_request_summary' | 'read_need_measurements' @@ -46,6 +56,8 @@ export type TerminalEnterpriseReadingStepView = TerminalEnterpriseReadingStepDef }; export type TerminalEnterpriseReadingUxStateInput = { + transactionId?: string | null; + routeReadingStage?: TerminalEnterpriseReadingStepId | null; hasRepositorySource?: boolean; hasReadMeasurement?: boolean; hasSynthesizedNeed?: boolean; @@ -54,10 +66,30 @@ export type TerminalEnterpriseReadingUxStateInput = { hasSourceSafePreview?: boolean; hasSettlementReadback?: boolean; hasDeliveryReadback?: boolean; + retryRequested?: boolean; + restartRequested?: boolean; + failureKind?: TerminalEnterpriseReadingFailureKind | null; sourceSafePreviewBlocked?: boolean; disclosureLeakageDetected?: boolean; }; +export type TerminalEnterpriseReadingRouteState = { + transactionId: string | null; + transactionIdPresent: boolean; + transactionIdRequiredForRecovery: true; + readingStageQueryParam: 'readingStage'; + activeStageHydratedFromRoute: boolean; + routeReadingStage: TerminalEnterpriseReadingStepId | null; + restartRequested: boolean; + restartRestoresActiveStage: true; + retryRequested: boolean; + retryPreservesNeedLineage: true; + retryPreservesSettlementBoundary: true; + failureKind: TerminalEnterpriseReadingFailureKind; + failureStateSourceSafe: true; + failureRepairActions: string[]; +}; + export type TerminalEnterpriseReadingUxState = { schema: 'bitcode.terminal.enterprise-reading-ux-state'; activeStepId: TerminalEnterpriseReadingStepId; @@ -78,10 +110,15 @@ export type TerminalEnterpriseReadingUxState = { routeContract: { terminalOwnsTransactionAuthority: true; conversationMayHandoffIntent: true; + transactionRouteRequiredForRecovery: true; acceptedNeedRequiredBeforeFindingFits: true; sourceSafePreviewRequiredBeforeSettlement: true; deliveryRequiresSettlementUnlock: true; + restartRestoresReadingStage: true; + retryPreservesSourceSafeLineage: true; + failureStatesSourceSafe: true; }; + routeState: TerminalEnterpriseReadingRouteState; proofRoot: string; }; @@ -183,6 +220,47 @@ export function inferTerminalEnterpriseReadingActiveStep( return 'request-read'; } +function normalizeTransactionId(value: string | null | undefined): string | null { + const normalized = value?.trim(); + return normalized ? normalized : null; +} + +function routeStageOrNull(value: TerminalEnterpriseReadingStepId | null | undefined): TerminalEnterpriseReadingStepId | null { + return value && STEP_ORDER.includes(value) ? value : null; +} + +function chooseActiveStep( + input: TerminalEnterpriseReadingUxStateInput, +): { activeStepId: TerminalEnterpriseReadingStepId; routeReadingStage: TerminalEnterpriseReadingStepId | null } { + const inferredStep = inferTerminalEnterpriseReadingActiveStep(input); + const routeReadingStage = routeStageOrNull(input.routeReadingStage); + if (!routeReadingStage) return { activeStepId: inferredStep, routeReadingStage }; + + const inferredIndex = STEP_ORDER.indexOf(inferredStep); + const routeIndex = STEP_ORDER.indexOf(routeReadingStage); + return { + activeStepId: routeIndex > inferredIndex ? routeReadingStage : inferredStep, + routeReadingStage, + }; +} + +function failureKindFor(input: TerminalEnterpriseReadingUxStateInput): TerminalEnterpriseReadingFailureKind { + if (input.disclosureLeakageDetected) return 'source_safety_blocked'; + if (input.sourceSafePreviewBlocked) return 'asset_pack_preview_blocked'; + return input.failureKind || 'none'; +} + +function repairActionsForFailure(kind: TerminalEnterpriseReadingFailureKind): string[] { + if (kind === 'none') return []; + if (kind === 'read_request_invalid') return ['repair-read-request']; + if (kind === 'need_review_required') return ['review-or-resynthesize-need']; + if (kind === 'fits_finding_failed') return ['retry-finding-fits-from-accepted-need']; + if (kind === 'asset_pack_preview_blocked') return ['repair-source-safe-preview']; + if (kind === 'settlement_blocked') return ['repair-settlement-readback']; + if (kind === 'delivery_blocked') return ['repair-repository-delivery']; + return ['repair-source-safety-disclosure']; +} + function blockersFor(stepId: TerminalEnterpriseReadingStepId, input: TerminalEnterpriseReadingUxStateInput) { const blockers: string[] = []; if (stepId === 'request-read' && !input.hasRepositorySource) blockers.push('repository source required'); @@ -208,8 +286,10 @@ function blockersFor(stepId: TerminalEnterpriseReadingStepId, input: TerminalEnt export function buildTerminalEnterpriseReadingUxState( input: TerminalEnterpriseReadingUxStateInput = {}, ): TerminalEnterpriseReadingUxState { - const activeStepId = inferTerminalEnterpriseReadingActiveStep(input); + const { activeStepId, routeReadingStage } = chooseActiveStep(input); const activeIndex = STEP_ORDER.indexOf(activeStepId); + const transactionId = normalizeTransactionId(input.transactionId); + const failureKind = failureKindFor(input); const steps = TERMINAL_ENTERPRISE_READING_STEPS.map((step, index) => { const blockers = blockersFor(step.id, input); const state: TerminalEnterpriseReadingStepState = @@ -232,6 +312,10 @@ export function buildTerminalEnterpriseReadingUxState( hasSourceSafePreview: Boolean(input.hasSourceSafePreview), hasSettlementReadback: Boolean(input.hasSettlementReadback), hasDeliveryReadback: Boolean(input.hasDeliveryReadback), + transactionId, + routeReadingStage, + retryRequested: Boolean(input.retryRequested), + failureKind, }); return { @@ -254,9 +338,29 @@ export function buildTerminalEnterpriseReadingUxState( routeContract: { terminalOwnsTransactionAuthority: true, conversationMayHandoffIntent: true, + transactionRouteRequiredForRecovery: true, acceptedNeedRequiredBeforeFindingFits: true, sourceSafePreviewRequiredBeforeSettlement: true, deliveryRequiresSettlementUnlock: true, + restartRestoresReadingStage: true, + retryPreservesSourceSafeLineage: true, + failureStatesSourceSafe: true, + }, + routeState: { + transactionId, + transactionIdPresent: Boolean(transactionId), + transactionIdRequiredForRecovery: true, + readingStageQueryParam: 'readingStage', + activeStageHydratedFromRoute: routeReadingStage === activeStepId, + routeReadingStage, + restartRequested: Boolean(input.restartRequested), + restartRestoresActiveStage: true, + retryRequested: Boolean(input.retryRequested), + retryPreservesNeedLineage: true, + retryPreservesSettlementBoundary: true, + failureKind, + failureStateSourceSafe: true, + failureRepairActions: repairActionsForFailure(failureKind), }, proofRoot: `terminal-enterprise-reading-ux:${stableHash(seed)}`, }; @@ -276,9 +380,19 @@ export function assertTerminalEnterpriseReadingUxStateSourceSafe(state: Terminal state.disclosure.ledgerAuthorityClaimed === false && state.routeContract.terminalOwnsTransactionAuthority === true && state.routeContract.conversationMayHandoffIntent === true && + state.routeContract.transactionRouteRequiredForRecovery === true && state.routeContract.acceptedNeedRequiredBeforeFindingFits === true && state.routeContract.sourceSafePreviewRequiredBeforeSettlement === true && state.routeContract.deliveryRequiresSettlementUnlock === true && + state.routeContract.restartRestoresReadingStage === true && + state.routeContract.retryPreservesSourceSafeLineage === true && + state.routeContract.failureStatesSourceSafe === true && + state.routeState.transactionIdRequiredForRecovery === true && + state.routeState.readingStageQueryParam === 'readingStage' && + state.routeState.restartRestoresActiveStage === true && + state.routeState.retryPreservesNeedLineage === true && + state.routeState.retryPreservesSettlementBoundary === true && + state.routeState.failureStateSourceSafe === true && TERMINAL_ENTERPRISE_READING_FORBIDDEN_FIELDS.every((field) => state.disclosure.hiddenBeforeSettlement.includes(field), ); diff --git a/uapi/tests/terminalEnterpriseReadingUxState.test.ts b/uapi/tests/terminalEnterpriseReadingUxState.test.ts index 352e54e5..52873233 100644 --- a/uapi/tests/terminalEnterpriseReadingUxState.test.ts +++ b/uapi/tests/terminalEnterpriseReadingUxState.test.ts @@ -90,6 +90,7 @@ describe('terminal-enterprise-reading-ux-state', () => { it('admits only source-safe low-detail and expandable metadata before settlement', () => { const state = buildTerminalEnterpriseReadingUxState({ + transactionId: 'reading-transaction-1', hasRepositorySource: true, hasReadMeasurement: true, hasSynthesizedNeed: true, @@ -98,13 +99,49 @@ describe('terminal-enterprise-reading-ux-state', () => { }); expect(state.activeStepId).toBe('request-fit'); + expect(state.routeState.transactionId).toBe('reading-transaction-1'); + expect(state.routeState.transactionIdPresent).toBe(true); + expect(state.routeState.readingStageQueryParam).toBe('readingStage'); expect(state.routeContract.acceptedNeedRequiredBeforeFindingFits).toBe(true); expect(state.routeContract.sourceSafePreviewRequiredBeforeSettlement).toBe(true); expect(state.routeContract.deliveryRequiresSettlementUnlock).toBe(true); + expect(state.routeContract.restartRestoresReadingStage).toBe(true); + expect(state.routeContract.retryPreservesSourceSafeLineage).toBe(true); + expect(state.routeContract.failureStatesSourceSafe).toBe(true); expect(state.proofRoot).toMatch(/^terminal-enterprise-reading-ux:/u); expect(assertTerminalEnterpriseReadingUxStateSourceSafe(state)).toEqual({ admitted: true, reason: 'source_safe_enterprise_reading_ux_metadata', }); }); + + it('hydrates later route stages, retry posture, and source-safe failure repair actions', () => { + const state = buildTerminalEnterpriseReadingUxState({ + transactionId: 'reading-transaction-2', + routeReadingStage: 'buy-asset-pack-settle', + hasRepositorySource: true, + hasReadMeasurement: true, + hasSynthesizedNeed: true, + hasAcceptedNeed: true, + retryRequested: true, + restartRequested: true, + failureKind: 'settlement_blocked', + }); + + expect(state.activeStepId).toBe('buy-asset-pack-settle'); + expect(state.routeState.routeReadingStage).toBe('buy-asset-pack-settle'); + expect(state.routeState.activeStageHydratedFromRoute).toBe(true); + expect(state.routeState.retryRequested).toBe(true); + expect(state.routeState.retryPreservesNeedLineage).toBe(true); + expect(state.routeState.retryPreservesSettlementBoundary).toBe(true); + expect(state.routeState.restartRequested).toBe(true); + expect(state.routeState.restartRestoresActiveStage).toBe(true); + expect(state.routeState.failureKind).toBe('settlement_blocked'); + expect(state.routeState.failureStateSourceSafe).toBe(true); + expect(state.routeState.failureRepairActions).toContain('repair-settlement-readback'); + expect(state.steps.find((step) => step.id === 'buy-asset-pack-settle')?.blockers).toContain( + 'source-safe AssetPack preview required', + ); + expect(assertTerminalEnterpriseReadingUxStateSourceSafe(state).admitted).toBe(true); + }); });