diff --git a/.bitcode/v44-economic-domain-model.json b/.bitcode/v44-economic-domain-model.json index a1566c56..2fcda699 100644 --- a/.bitcode/v44-economic-domain-model.json +++ b/.bitcode/v44-economic-domain-model.json @@ -5,7 +5,7 @@ "currentTarget": "V43", "sourceSafetyVerdict": "source-safe-economic-domain-model-metadata", "generatedAt": "deterministic", - "artifactRoot": "v44-economic-domain-model:1ac488c93f2cbc4464d79d2f", + "artifactRoot": "v44-economic-domain-model:ec44bc6e9df92162dfa92bfe", "passed": true, "economicObjectIds": [ "EnterprisePackPortfolio", @@ -759,18 +759,18 @@ ], "sourceRoots": { "activePointer": "BITCODE_SPEC.txt:4ea77b214c66f69c697bff3d", - "spec": "BITCODE_SPEC_V44.md:61a14ac02f93d45980135444", - "delta": "BITCODE_SPEC_V44_DELTA.md:8e456398e6b8f70ce1c79bf6", - "notes": "BITCODE_SPEC_V44_NOTES.md:427bff37cf91930869a4ec4b", - "parity": "BITCODE_SPEC_V44_PARITY_MATRIX.md:95b96fd368ff03e218ce4d9e", - "roadmap": "SPECIFICATIONS_ROADMAP.md:265ab18c2e4e40b44c53e6c2", - "readme": "README.md:c3d54908be1513348a80fce9", - "protocolReadme": "packages/protocol/README.md:32f06e276fcd832dd7b87195", - "packageJson": "package.json:7a1928fa7ee07cb9e5a0ccea", - "gateWorkflow": ".github/workflows/bitcode-gate-quality.yml:e599b8492c1b1889903c105c", - "canonWorkflow": ".github/workflows/bitcode-canon-quality.yml:23259d5fe5601792812544f3", - "packageIndex": "packages/protocol/src/index.js:6d944931866655f14635448d", - "packageTypes": "packages/protocol/src/index.d.ts:fc425e096922043863600369", + "spec": "BITCODE_SPEC_V44.md:c5081509af1ff529f93578c2", + "delta": "BITCODE_SPEC_V44_DELTA.md:3bb300cfcf9848e84dd567b8", + "notes": "BITCODE_SPEC_V44_NOTES.md:2fb9c45ff4d5aab74d4877f1", + "parity": "BITCODE_SPEC_V44_PARITY_MATRIX.md:3e6dbeeeb9c4acf7949e55b3", + "roadmap": "SPECIFICATIONS_ROADMAP.md:b68526af5b69fcfd077db62a", + "readme": "README.md:57205081ca8e0253edb60d83", + "protocolReadme": "packages/protocol/README.md:503724d451dd53ba1895cedf", + "packageJson": "package.json:60ace742266c6fa5ef22049b", + "gateWorkflow": ".github/workflows/bitcode-gate-quality.yml:1e58eb822e2b550fc7c7fcba", + "canonWorkflow": ".github/workflows/bitcode-canon-quality.yml:6d06d4145fc763240d239fe7", + "packageIndex": "packages/protocol/src/index.js:e359325bd742aff90897b6ec", + "packageTypes": "packages/protocol/src/index.d.ts:4904191a7254fdf41b8b79ff", "packageSource": "packages/protocol/src/canonical/v44-economic-domain-model.js:21b8f5c80ad8322dff2909ed", "packageTest": "packages/protocol/test/v44-economic-domain-model.test.js:b89f71aa0cf578dabbec3cd9", "generator": "scripts/generate-v44-economic-domain-model.mjs:3390fbc087e919bedb14550c", diff --git a/.bitcode/v44-packs-portfolio-market-intelligence.json b/.bitcode/v44-packs-portfolio-market-intelligence.json index d476c010..4f125dd9 100644 --- a/.bitcode/v44-packs-portfolio-market-intelligence.json +++ b/.bitcode/v44-packs-portfolio-market-intelligence.json @@ -5,7 +5,7 @@ "currentTarget": "V43", "sourceSafetyVerdict": "source-safe-packs-portfolio-market-intelligence-metadata", "generatedAt": "deterministic", - "artifactRoot": "v44-packs-portfolio-market-intelligence:4f9b4a7ab99f67bcc65e6701", + "artifactRoot": "v44-packs-portfolio-market-intelligence:a8a9feee64f670ea7bed9afc", "passed": true, "portfolioViewIds": [ "enterprise-pack-portfolio", @@ -109,23 +109,23 @@ ], "sourceRoots": { "activePointer": "BITCODE_SPEC.txt:4ea77b214c66f69c697bff3d", - "spec": "BITCODE_SPEC_V44.md:61a14ac02f93d45980135444", - "delta": "BITCODE_SPEC_V44_DELTA.md:8e456398e6b8f70ce1c79bf6", - "notes": "BITCODE_SPEC_V44_NOTES.md:427bff37cf91930869a4ec4b", - "parity": "BITCODE_SPEC_V44_PARITY_MATRIX.md:95b96fd368ff03e218ce4d9e", - "roadmap": "SPECIFICATIONS_ROADMAP.md:265ab18c2e4e40b44c53e6c2", - "readme": "README.md:c3d54908be1513348a80fce9", - "protocolReadme": "packages/protocol/README.md:32f06e276fcd832dd7b87195", - "packageJson": "package.json:7a1928fa7ee07cb9e5a0ccea", - "gateWorkflow": ".github/workflows/bitcode-gate-quality.yml:e599b8492c1b1889903c105c", - "canonWorkflow": ".github/workflows/bitcode-canon-quality.yml:23259d5fe5601792812544f3", + "spec": "BITCODE_SPEC_V44.md:c5081509af1ff529f93578c2", + "delta": "BITCODE_SPEC_V44_DELTA.md:3bb300cfcf9848e84dd567b8", + "notes": "BITCODE_SPEC_V44_NOTES.md:2fb9c45ff4d5aab74d4877f1", + "parity": "BITCODE_SPEC_V44_PARITY_MATRIX.md:3e6dbeeeb9c4acf7949e55b3", + "roadmap": "SPECIFICATIONS_ROADMAP.md:b68526af5b69fcfd077db62a", + "readme": "README.md:57205081ca8e0253edb60d83", + "protocolReadme": "packages/protocol/README.md:503724d451dd53ba1895cedf", + "packageJson": "package.json:60ace742266c6fa5ef22049b", + "gateWorkflow": ".github/workflows/bitcode-gate-quality.yml:1e58eb822e2b550fc7c7fcba", + "canonWorkflow": ".github/workflows/bitcode-canon-quality.yml:6d06d4145fc763240d239fe7", "economicModel": "packages/protocol/src/canonical/v44-economic-domain-model.js:21b8f5c80ad8322dff2909ed", "model": "uapi/components/base/bitcode/activity/pack-activity-model.ts:77058cc82039a3bd1b7514bf", "route": "uapi/app/api/packs/activity/route.ts:97c5a4539d48562eb5de66e8", "client": "uapi/app/packs/PacksPageClient.tsx:b70aa97a8022a9c42995a030", "uapiTest": "uapi/tests/packActivityModel.test.ts:541e71f2806dc1497379d0f1", - "packageIndex": "packages/protocol/src/index.js:6d944931866655f14635448d", - "packageTypes": "packages/protocol/src/index.d.ts:fc425e096922043863600369", + "packageIndex": "packages/protocol/src/index.js:e359325bd742aff90897b6ec", + "packageTypes": "packages/protocol/src/index.d.ts:4904191a7254fdf41b8b79ff", "packageTest": "packages/protocol/test/v44-packs-portfolio-market-intelligence.test.js:ea02fa03d210e6480036fc0f", "generator": "scripts/generate-v44-packs-portfolio-market-intelligence.mjs:c106ef7138e3a330d405bec2", "checker": "scripts/check-v44-gate3-packs-portfolio-market-intelligence.mjs:0de0b87cbc96bb307cc1533a" diff --git a/.bitcode/v44-reading-budget-quote-policy.json b/.bitcode/v44-reading-budget-quote-policy.json new file mode 100644 index 00000000..f7534b2e --- /dev/null +++ b/.bitcode/v44-reading-budget-quote-policy.json @@ -0,0 +1,340 @@ +{ + "artifactId": "v44-reading-budget-quote-policy", + "schemaId": "bitcode.v44.readingBudgetQuotePolicy.v1", + "version": "V44", + "currentTarget": "V43", + "sourceSafetyVerdict": "source-safe-reading-budget-quote-policy-metadata", + "generatedAt": "deterministic", + "artifactRoot": "v44-reading-budget-quote-policy:8ee2dddcc13bb816acf6e881", + "passed": true, + "objectIds": [ + "ReadingBudgetPolicy", + "AssetPackQuotePolicy", + "ProcurementApprovalReceipt", + "BuyerAuthorizationReceipt", + "PrePurchaseReviewBoundary", + "BtcBtdSettlementReadiness" + ], + "budgetStateIds": [ + "awaiting-quote", + "within-budget", + "approval-required", + "exceeded" + ], + "quoteStateIds": [ + "awaiting-preview", + "quoted", + "expired", + "approved", + "blocked" + ], + "settlementReadinessIds": [ + "awaiting-preview", + "awaiting-approval", + "awaiting-buyer-authority", + "awaiting-wallet-authority", + "ready-for-testnet-settlement", + "blocked-budget", + "blocked-expired-quote" + ], + "forbiddenPayloadIds": [ + "protected-source-payloads", + "unpaid-assetpack-source", + "source-snippets", + "raw-prompts", + "interpolated-prompts", + "raw-provider-responses", + "credentials", + "wallet-private-material", + "private-settlement-payloads", + "value-bearing-mainnet-admission" + ], + "rows": [ + { + "rowId": "reading-budget-policy", + "owner": "uapi/app/read/read-route-model.ts", + "contract": "ReadProcurementGovernance projects source-safe budget envelopes, approval thresholds, quote sats, and fail-closed budget states for Reading.", + "requiredFields": [ + "budgetEnvelopeSats", + "approvalThresholdSats", + "quoteSats", + "approvalRequired", + "policyRoot" + ], + "rowRoot": "v44-reading-budget-quote-row:de4aed16a7c86b696a887ba4", + "sourceSafeMetadataOnly": true, + "protectedSourceVisible": false, + "unpaidAssetPackSourceVisible": false + }, + { + "rowId": "assetpack-quote-policy", + "owner": "uapi/app/read/read-route-model.ts", + "contract": "AssetPack quote policy uses measurement-weight-volume pricing, quote expiry state, BTC fee asset labeling, and quote roots before settlement.", + "requiredFields": [ + "measurement-weight-volume", + "quoteRoot", + "expiresAt", + "feeAsset", + "reading-share-to-fee" + ], + "rowRoot": "v44-reading-budget-quote-row:06d747a0fbab1ef7d9cf6209", + "sourceSafeMetadataOnly": true, + "protectedSourceVisible": false, + "unpaidAssetPackSourceVisible": false + }, + { + "rowId": "procurement-approval-receipt", + "owner": "uapi/app/read/read-route-model.ts", + "contract": "Procurement approval records buyer authorization, wallet authority, reviewer approval posture, blockers, and approval roots before settlement.", + "requiredFields": [ + "buyerAuthorized", + "walletAuthorityPresent", + "procurementApproved", + "approvalRoot" + ], + "rowRoot": "v44-reading-budget-quote-row:864754b8e29561807c475c36", + "sourceSafeMetadataOnly": true, + "protectedSourceVisible": false, + "unpaidAssetPackSourceVisible": false + }, + { + "rowId": "pre-purchase-review-boundary", + "owner": "uapi/app/read/read-route-model.ts", + "contract": "Pre-purchase review stays source-safe: preview metadata may be visible, but protected source, unpaid AssetPack source, wallet private material, and private settlement payloads remain hidden.", + "requiredFields": [ + "protectedSourceVisible", + "unpaidAssetPackSourceVisible", + "walletPrivateMaterialVisible", + "settlementPrivatePayloadVisible" + ], + "rowRoot": "v44-reading-budget-quote-row:0c899e931c10974d14ed6523", + "sourceSafeMetadataOnly": true, + "protectedSourceVisible": false, + "unpaidAssetPackSourceVisible": false + }, + { + "rowId": "read-route-procurement-ui", + "owner": "uapi/app/read/ReadPageClient.tsx", + "contract": "/read renders budget, quote, approval, settlement readiness, wallet authority, and procurement blockers beside the five-step Reading path.", + "requiredFields": [ + "Budget and quote", + "procurementRows", + "formatSats", + "wallet authority" + ], + "rowRoot": "v44-reading-budget-quote-row:33e9d3bc18cadf0524c70196", + "sourceSafeMetadataOnly": true, + "protectedSourceVisible": false, + "unpaidAssetPackSourceVisible": false + }, + { + "rowId": "deterministic-share-to-fee-policy", + "owner": "packages/btd/src/source-to-shares.ts", + "contract": "Reading quote governance binds to source-to-shares and BTC fee primitives so share-to-fee estimates can later reconcile to payment, rights, and allocation receipts.", + "requiredFields": [ + "feeQuote", + "grossSats", + "settlementAllocations", + "settlementConservation" + ], + "rowRoot": "v44-reading-budget-quote-row:25c2b96c67bcb907f55a6177", + "sourceSafeMetadataOnly": true, + "protectedSourceVisible": false, + "unpaidAssetPackSourceVisible": false + } + ], + "sourceRoots": { + "activePointer": "BITCODE_SPEC.txt:4ea77b214c66f69c697bff3d", + "spec": "BITCODE_SPEC_V44.md:c5081509af1ff529f93578c2", + "delta": "BITCODE_SPEC_V44_DELTA.md:3bb300cfcf9848e84dd567b8", + "notes": "BITCODE_SPEC_V44_NOTES.md:2fb9c45ff4d5aab74d4877f1", + "parity": "BITCODE_SPEC_V44_PARITY_MATRIX.md:3e6dbeeeb9c4acf7949e55b3", + "roadmap": "SPECIFICATIONS_ROADMAP.md:b68526af5b69fcfd077db62a", + "readme": "README.md:57205081ca8e0253edb60d83", + "protocolReadme": "packages/protocol/README.md:503724d451dd53ba1895cedf", + "packageJson": "package.json:60ace742266c6fa5ef22049b", + "gateWorkflow": ".github/workflows/bitcode-gate-quality.yml:1e58eb822e2b550fc7c7fcba", + "canonWorkflow": ".github/workflows/bitcode-canon-quality.yml:6d06d4145fc763240d239fe7", + "economicModel": "packages/protocol/src/canonical/v44-economic-domain-model.js:21b8f5c80ad8322dff2909ed", + "readModel": "uapi/app/read/read-route-model.ts:a31ae532904b0ba357eb9e67", + "readClient": "uapi/app/read/ReadPageClient.tsx:8c810ed63b6cf895cead0a01", + "readModelTest": "uapi/tests/readRouteModel.test.ts:622a125a72c8457baf4c9d6d", + "sourceToShares": "packages/btd/src/source-to-shares.ts:6e2e84251b29ba2477766708", + "btcFeeOperation": "packages/btd/src/btc-fee-operation.ts:65f910e8e471fe199939e9c4", + "packageIndex": "packages/protocol/src/index.js:e359325bd742aff90897b6ec", + "packageTypes": "packages/protocol/src/index.d.ts:4904191a7254fdf41b8b79ff", + "packageSource": "packages/protocol/src/canonical/v44-reading-budget-quote-policy.js:d663034fe86ccbe85d5749f5", + "packageTest": "packages/protocol/test/v44-reading-budget-quote-policy.test.js:2d9b19c509e0b28a829c44a3", + "generator": "scripts/generate-v44-reading-budget-quote-policy.mjs:e2c376cb03ef80c210eef688", + "checker": "scripts/check-v44-gate4-reading-budget-quote-policy.mjs:f61d5f8a81e2b1c55eb57c6c" + }, + "predicateResults": [ + { + "id": "active-canon-pointer-remains-v43", + "sourcePath": "BITCODE_SPEC.txt", + "passed": true + }, + { + "id": "spec-defines-gate4", + "sourcePath": "BITCODE_SPEC_V44.md", + "passed": true + }, + { + "id": "spec-names-gate4-artifact", + "sourcePath": "BITCODE_SPEC_V44.md", + "passed": true + }, + { + "id": "delta-records-gate4", + "sourcePath": "BITCODE_SPEC_V44_DELTA.md", + "passed": true + }, + { + "id": "notes-records-gate4", + "sourcePath": "BITCODE_SPEC_V44_NOTES.md", + "passed": true + }, + { + "id": "parity-records-gate4", + "sourcePath": "BITCODE_SPEC_V44_PARITY_MATRIX.md", + "passed": true + }, + { + "id": "roadmap-records-gate4", + "sourcePath": "SPECIFICATIONS_ROADMAP.md", + "passed": true + }, + { + "id": "readme-records-gate4", + "sourcePath": "README.md", + "passed": true + }, + { + "id": "protocol-readme-records-gate4", + "sourcePath": "packages/protocol/README.md", + "passed": true + }, + { + "id": "economic-model-prerequisite-present", + "sourcePath": "packages/protocol/src/canonical/v44-economic-domain-model.js", + "passed": true + }, + { + "id": "read-model-defines-procurement-governance", + "sourcePath": "uapi/app/read/read-route-model.ts", + "passed": true + }, + { + "id": "read-model-defines-budget-states", + "sourcePath": "uapi/app/read/read-route-model.ts", + "passed": true + }, + { + "id": "read-model-defines-quote-states", + "sourcePath": "uapi/app/read/read-route-model.ts", + "passed": true + }, + { + "id": "read-model-defines-settlement-readiness", + "sourcePath": "uapi/app/read/read-route-model.ts", + "passed": true + }, + { + "id": "read-model-defines-source-safe-review-boundary", + "sourcePath": "uapi/app/read/read-route-model.ts", + "passed": true + }, + { + "id": "read-client-renders-budget-quote", + "sourcePath": "uapi/app/read/ReadPageClient.tsx", + "passed": true + }, + { + "id": "read-client-renders-wallet-authority", + "sourcePath": "uapi/app/read/ReadPageClient.tsx", + "passed": true + }, + { + "id": "read-model-test-covers-governance", + "sourcePath": "uapi/tests/readRouteModel.test.ts", + "passed": true + }, + { + "id": "source-to-shares-prerequisite-present", + "sourcePath": "packages/btd/src/source-to-shares.ts", + "passed": true + }, + { + "id": "btc-fee-operation-prerequisite-present", + "sourcePath": "packages/btd/src/btc-fee-operation.ts", + "passed": true + }, + { + "id": "package-test-covers-gate4", + "sourcePath": "packages/protocol/test/v44-reading-budget-quote-policy.test.js", + "passed": true + }, + { + "id": "package-exports-gate4", + "sourcePath": "packages/protocol/src/index.js", + "passed": true + }, + { + "id": "package-types-export-gate4", + "sourcePath": "packages/protocol/src/index.d.ts", + "passed": true + }, + { + "id": "package-json-exposes-gate4", + "sourcePath": "package.json", + "passed": true + }, + { + "id": "gate-workflow-runs-gate4", + "sourcePath": ".github/workflows/bitcode-gate-quality.yml", + "passed": true + }, + { + "id": "canon-workflow-runs-gate4", + "sourcePath": ".github/workflows/bitcode-canon-quality.yml", + "passed": true + }, + { + "id": "generator-exists", + "sourcePath": "scripts/generate-v44-reading-budget-quote-policy.mjs", + "passed": true + }, + { + "id": "checker-exists", + "sourcePath": "scripts/check-v44-gate4-reading-budget-quote-policy.mjs", + "passed": true + } + ], + "coverage": { + "budgetEnvelopeImplemented": true, + "approvalThresholdImplemented": true, + "quoteExpiryImplemented": true, + "deterministicShareToFeeImplemented": true, + "buyerAuthorizationImplemented": true, + "walletAuthorityImplemented": true, + "btcBtdSettlementReadinessImplemented": true, + "sourceSafePrePurchaseReviewImplemented": true, + "readRouteUiImplemented": true, + "noSourceLeakTestsImplemented": true, + "sourceSafeMetadataOnly": true, + "protectedSourceVisible": false, + "rawSourceTextVisible": false, + "sourceSnippetVisible": false, + "rawPromptVisible": false, + "interpolatedPromptVisible": false, + "rawProviderResponseVisible": false, + "unpaidAssetPackSourceVisible": false, + "credentialsSerialized": false, + "walletPrivateMaterialVisible": false, + "settlementPrivatePayloadVisible": false, + "valueBearingMainnetAdmitted": false, + "requiredPredicateCount": 28, + "passedPredicateCount": 28, + "failedPredicateIds": [] + } +} diff --git a/.github/workflows/bitcode-canon-quality.yml b/.github/workflows/bitcode-canon-quality.yml index 696fa246..daa5b774 100644 --- a/.github/workflows/bitcode-canon-quality.yml +++ b/.github/workflows/bitcode-canon-quality.yml @@ -370,6 +370,9 @@ jobs: if [ -f scripts/check-v44-gate3-packs-portfolio-market-intelligence.mjs ]; then node scripts/check-v44-gate3-packs-portfolio-market-intelligence.mjs --skip-branch-check --skip-uapi-tests --skip-package-tests fi + if [ -f scripts/check-v44-gate4-reading-budget-quote-policy.mjs ]; then + node scripts/check-v44-gate4-reading-budget-quote-policy.mjs --skip-branch-check --skip-uapi-tests --skip-package-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 bc0e2e90..3e71d29a 100644 --- a/.github/workflows/bitcode-gate-quality.yml +++ b/.github/workflows/bitcode-gate-quality.yml @@ -499,6 +499,9 @@ jobs: if [ -f scripts/check-v44-gate3-packs-portfolio-market-intelligence.mjs ]; then node scripts/check-v44-gate3-packs-portfolio-market-intelligence.mjs --skip-branch-check --skip-uapi-tests --skip-package-tests fi + if [ -f scripts/check-v44-gate4-reading-budget-quote-policy.mjs ]; then + node scripts/check-v44-gate4-reading-budget-quote-policy.mjs --skip-branch-check --skip-uapi-tests --skip-package-tests + fi fi else echo "Unexpected BITCODE_SPEC.txt pointer: $POINTER" >&2 @@ -540,6 +543,9 @@ jobs: if [ "$POINTER" = "V43" ] && [ -f scripts/check-v44-gate3-packs-portfolio-market-intelligence.mjs ]; then node scripts/check-v44-gate3-packs-portfolio-market-intelligence.mjs --skip-branch-check --skip-uapi-tests --skip-package-tests fi + if [ "$POINTER" = "V43" ] && [ -f scripts/check-v44-gate4-reading-budget-quote-policy.mjs ]; then + node scripts/check-v44-gate4-reading-budget-quote-policy.mjs --skip-branch-check --skip-uapi-tests --skip-package-tests + fi if [ "$POINTER" != "V43" ]; then if [ -f scripts/check-v43-gate1-packs-read-deposit-roadmap.mjs ]; then node scripts/check-v43-gate1-packs-read-deposit-roadmap.mjs --skip-branch-check diff --git a/BITCODE_SPEC_V44.md b/BITCODE_SPEC_V44.md index 46bc282b..3ca127ac 100644 --- a/BITCODE_SPEC_V44.md +++ b/BITCODE_SPEC_V44.md @@ -3,12 +3,12 @@ ## Status - Version: `V44` -- V44 state: draft opened for scaled engineering economy and tokenized AssetPack network work over promoted V43 product routes +- V44 state: draft Gate 4 Reading budget quote policy work over promoted V43 product routes - Current canonical/latest target: `V43` - Prior canonical anchor: `BITCODE_SPEC_V43.md` - Prior generated proof appendix: `BITCODE_SPEC_V43_PROVEN.md` -- Generated structured artifact inventory: draft `.bitcode/v44-*` artifacts begin with the V44 Gate 1 roadmap/spec posture and must remain source-safe metadata only until later V44 gates generate package-backed reports -- Source parity state: V44 begins from promoted `/packs`, `/read`, `/deposit`, agentic Depositing, five-step Reading, BTD/BTC settlement, and PackActivity canon; Gate 1 is specification and workflow posture only +- Generated structured artifact inventory: draft `.bitcode/v44-*` artifacts now include Gate 2 economic domain, Gate 3 Packs portfolio market intelligence, and Gate 4 Reading budget quote policy reports; all remain source-safe metadata only +- Source parity state: V44 begins from promoted `/packs`, `/read`, `/deposit`, agentic Depositing, five-step Reading, BTD/BTC settlement, and PackActivity canon; Gate 4 binds `/read` budget and quote policy without changing settlement law - Notes companion: `BITCODE_SPEC_V44_NOTES.md` - Delta companion: `BITCODE_SPEC_V44_DELTA.md` - Parity companion: `BITCODE_SPEC_V44_PARITY_MATRIX.md` @@ -226,6 +226,20 @@ Gate 4 must bind Reading spend controls: budget envelopes, approval thresholds, quote expiry, deterministic share-to-fee policy, buyer authorization, BTC/BTD settlement readiness, and source-safe pre-purchase review. +Gate 4 closes through `V44ReadingBudgetQuotePolicy` in +`packages/protocol/src/canonical/v44-reading-budget-quote-policy.js`, +deterministic `.bitcode/v44-reading-budget-quote-policy.json`, +`generate:v44-reading-budget-quote-policy`, +`check:v44-reading-budget-quote-policy`, and `check:v44-gate4`. The `/read` +route session now projects `ReadProcurementGovernance` with source-safe budget +policy, quote policy, procurement approval, buyer and wallet authority, +settlement readiness blockers, pre-purchase review roots, and deterministic +measurement-weight-volume share-to-fee calculation. `/read` renders those +budget, quote, approval, wallet authority, and settlement-readiness fields +without exposing protected source, unpaid AssetPack source, raw prompts, raw +provider responses, wallet private material, private settlement payloads, or +value-bearing mainnet admission. + ## V44 Gate 5 Depositor Earnings, ROI, And Supply Opportunity Intelligence Gate 5 must bind deposit-side economic clarity: likely demand, unfit Need @@ -592,7 +606,7 @@ quality, accessibility, visual inspectability, and generated quality evidence. | `.bitcode/v44-canon-posture-drift-report.json` | active/draft posture | draft-required | | `.bitcode/v44-economic-domain-model.json` | economic domain model | implemented-source-safe | | `.bitcode/v44-packs-portfolio-market-intelligence.json` | Packs portfolio and market intelligence | implemented-source-safe | -| `.bitcode/v44-reading-budget-quote-policy.json` | Reading budget and quote policy | planned | +| `.bitcode/v44-reading-budget-quote-policy.json` | Reading budget and quote policy | implemented-source-safe | | `.bitcode/v44-depositor-earnings-supply-opportunities.json` | depositor earnings and supply opportunities | planned | | `.bitcode/v44-btd-btc-compensation-statements.json` | BTD/BTC compensation statements | planned | | `.bitcode/v44-organization-policy-wallet-authority.json` | organization policy and wallet authority | planned | @@ -665,6 +679,9 @@ Gate 3 validates with `pnpm run generate:v44-packs-portfolio-market-intelligence`, `pnpm run check:v44-packs-portfolio-market-intelligence`, and `pnpm run check:v44-gate3`. +Gate 4 validates with `pnpm run generate:v44-reading-budget-quote-policy`, +`pnpm run check:v44-reading-budget-quote-policy`, and +`pnpm run check:v44-gate4`. Shared draft posture validates with `node scripts/check-bitcode-spec-family.mjs --version V44 --mode draft --current-target V43`, `node scripts/check-bitcode-canon-posture-drift.mjs --active-canon V43 --draft-target V44`, @@ -738,7 +755,7 @@ Inherited. | `.bitcode/v44-canon-posture-drift-report.json` | active/draft posture | draft-required | | `.bitcode/v44-economic-domain-model.json` | economic domain model | implemented-source-safe | | `.bitcode/v44-packs-portfolio-market-intelligence.json` | portfolio and market intelligence | implemented-source-safe | -| `.bitcode/v44-reading-budget-quote-policy.json` | budget and quote policy | planned | +| `.bitcode/v44-reading-budget-quote-policy.json` | budget and quote policy | implemented-source-safe | | `.bitcode/v44-depositor-earnings-supply-opportunities.json` | depositor earning opportunity | planned | | `.bitcode/v44-btd-btc-compensation-statements.json` | BTD/BTC/source-to-shares statement | planned | | `.bitcode/v44-organization-policy-wallet-authority.json` | organization policy and wallet authority | planned | diff --git a/BITCODE_SPEC_V44_DELTA.md b/BITCODE_SPEC_V44_DELTA.md index 21d0761a..b4cc23aa 100644 --- a/BITCODE_SPEC_V44_DELTA.md +++ b/BITCODE_SPEC_V44_DELTA.md @@ -3,7 +3,7 @@ ## Status - Version: `V44` -- V44 state: draft Gate 3 Packs portfolio market intelligence work over promoted V43 +- V44 state: draft Gate 4 Reading budget quote policy work over promoted V43 - Current canonical/latest target: `V43` - Prior canonical anchor: `BITCODE_SPEC_V43.md` - Prior generated proof appendix: `BITCODE_SPEC_V43_PROVEN.md` @@ -65,6 +65,10 @@ V44 Gate 3 closes `/packs` portfolio and market intelligence over the existing PackActivity source-safe API. It does not implement quote approval, payment observation, contributor payouts, wallet authority, or scaled rehearsals. +V44 Gate 4 closes `/read` budget and quote governance around source-safe +AssetPack previews. It does not observe payment, transfer BTD rights, execute +contributor payouts, or admit value-bearing mainnet operation. + ## Pre-Implementation Sequence 1. Open V44 spec family, roadmap, checker, package script, workflow posture, @@ -89,6 +93,9 @@ Gate 3 validates with `pnpm run generate:v44-packs-portfolio-market-intelligence`, `pnpm run check:v44-packs-portfolio-market-intelligence`, and `pnpm run check:v44-gate3`. +Gate 4 validates with `pnpm run generate:v44-reading-budget-quote-policy`, +`pnpm run check:v44-reading-budget-quote-policy`, and +`pnpm run check:v44-gate4`. Shared draft posture validates with `node scripts/check-bitcode-spec-family.mjs --version V44 --mode draft --current-target V43`, `node scripts/check-bitcode-canon-posture-drift.mjs --active-canon V43 --draft-target V44`, diff --git a/BITCODE_SPEC_V44_NOTES.md b/BITCODE_SPEC_V44_NOTES.md index e311b0c3..6779f214 100644 --- a/BITCODE_SPEC_V44_NOTES.md +++ b/BITCODE_SPEC_V44_NOTES.md @@ -3,12 +3,12 @@ ## Status - Version: `V44` -- V44 state: draft notes include Gate 3 Packs portfolio market intelligence work over promoted V43 +- V44 state: draft notes include Gate 4 Reading budget quote policy work over promoted V43 - Current canonical/latest target: `V43` - Prior canonical anchor: `BITCODE_SPEC_V43.md` - Prior generated proof appendix: `BITCODE_SPEC_V43_PROVEN.md` -- Generated structured artifact inventory: Gate 2 adds deterministic `.bitcode/v44-economic-domain-model.json`; Gate 3 adds deterministic `.bitcode/v44-packs-portfolio-market-intelligence.json` -- Source parity state: notes align roadmap, docs, workflow posture, package source, generated artifact, receipt taxonomy, and `/packs` portfolio market intelligence for active V43 / draft V44 +- Generated structured artifact inventory: Gate 2 adds deterministic `.bitcode/v44-economic-domain-model.json`; Gate 3 adds deterministic `.bitcode/v44-packs-portfolio-market-intelligence.json`; Gate 4 adds deterministic `.bitcode/v44-reading-budget-quote-policy.json` +- Source parity state: notes align roadmap, docs, workflow posture, package source, generated artifact, receipt taxonomy, `/packs` portfolio market intelligence, and `/read` budget quote policy for active V43 / draft V44 - Scope: V44 notes for digitizing and tokenizing scaled engineering economies through enterprise AssetPack portfolio, market intelligence, BTD/BTC accounting, governance, and compensation operation - Last fully realized canonical target preserved in source: `V43` @@ -93,7 +93,18 @@ network-rehearsal receipts. Its value labels remain estimate, quote, observed payment, final settlement, contributor allocation, delivery, and repair state. This is intentionally source-safe: it carries identifiers, roots, states, and contracts, not protected source, unpaid AssetPack source, raw prompts, provider -payloads, credentials, wallet private material, or private settlement payloads. +responses, credentials, wallet private material, private settlement payloads, +or value-bearing mainnet admission. + +## Gate 4 note + +Gate 4 makes `/read` budgeted without changing Reading law. The route can show +budget envelope state, quote state, approval posture, buyer and wallet +authority, deterministic measurement-weight-volume share-to-fee calculation, +and settlement readiness blockers before purchase. It cannot reveal source or +settlement private material, and it cannot treat a quote as final settlement. +Payment observation, BTD rights transfer, delivery unlock, and contributor +compensation remain later receipt-backed steps. ## Gate 3 note diff --git a/BITCODE_SPEC_V44_PARITY_MATRIX.md b/BITCODE_SPEC_V44_PARITY_MATRIX.md index 72fe84d1..b59c467b 100644 --- a/BITCODE_SPEC_V44_PARITY_MATRIX.md +++ b/BITCODE_SPEC_V44_PARITY_MATRIX.md @@ -3,7 +3,7 @@ ## Status - Version: `V44` -- V44 state: draft parity includes Gate 3 Packs portfolio market intelligence work +- V44 state: draft parity includes Gate 4 Reading budget quote policy work - Current canonical/latest target: `V43` - Prior canonical anchor: `BITCODE_SPEC_V43.md` - Prior generated proof appendix: `BITCODE_SPEC_V43_PROVEN.md` @@ -31,7 +31,7 @@ artifacts, workflow checks, and local/staging rehearsal receipts. | Gate 1 roadmap | V43 active / V44 draft posture, spec family, docs, workflow, package script, checker | implemented | | Economic domain model | Portfolio positions, market signals, quote states, settlement states, statements, repair cases | implemented | | Packs portfolio | `/packs` portfolio search, market intelligence, saved filters, economic facets | implemented | -| Reading procurement | Budget, quote policy, approval thresholds, purchase governance | drafted | +| Reading procurement | Budget, quote policy, approval thresholds, purchase governance through `.bitcode/v44-reading-budget-quote-policy.json` | implemented | | Depositor earnings | ROI, demand, compensation opportunity, supply recommendations | drafted | | Accounting statements | BTD/BTC/source-to-shares statements and reconciliation | drafted | | Organization governance | Roles, budgets, wallet authority, source criticality, approvals | drafted | @@ -46,7 +46,7 @@ artifacts, workflow checks, and local/staging rehearsal receipts. | Source safety | No protected source, unpaid source, raw prompts, provider payloads, credentials, wallet secrets | implemented | | Economic labels | Estimate, quote, observed payment, settlement, allocation, delivery, repair are distinct | implemented | | Ledger reconciliation | Portfolio statements reconcile to receipts before finality | drafted | -| Route authority | `/packs`, `/read`, `/deposit` do not bypass protocol law | drafted | +| Route authority | `/packs`, `/read`, `/deposit` do not bypass protocol law | implemented prerequisite | | Tests and proofs | Each gate has generated artifacts, package tests, route tests, and workflow checks | implemented prerequisite | ## Accepted boundaries diff --git a/README.md b/README.md index 4cc5e1c3..d90a5e2f 100644 --- a/README.md +++ b/README.md @@ -310,6 +310,18 @@ and market intelligence with saved filters, organization views, demand/supply signals, unfit Need signals, settlement and compensation facets, proof-root drilldown, API/UI projection tests, and no-source-leak checks. +V44 Gate 4 adds `V44ReadingBudgetQuotePolicy`, +`.bitcode/v44-reading-budget-quote-policy.json`, +`generate:v44-reading-budget-quote-policy`, +`check:v44-reading-budget-quote-policy`, and `check:v44-gate4`. It binds +`/read` to source-safe `ReadProcurementGovernance`: budget envelopes, approval +thresholds, quote expiry, deterministic measurement-weight-volume +share-to-fee policy, buyer authorization, wallet authority, BTC/BTD +settlement readiness blockers, pre-purchase review boundaries, route UI +readback, and tests that continue withholding protected source, unpaid +AssetPack source, raw prompts, provider payloads, wallet private material, +private settlement payloads, and value-bearing mainnet operation. + Exchange is inherited V36 canon: market-wide activity master-detail, buy/sell/ bid/ask/cancel/accept/settle/history flows, AssetPack range trading, rights-transfer review, pricing/liquidity/wrapper analysis, settlement diff --git a/SPECIFICATIONS_ROADMAP.md b/SPECIFICATIONS_ROADMAP.md index 52a1940b..63f75d19 100644 --- a/SPECIFICATIONS_ROADMAP.md +++ b/SPECIFICATIONS_ROADMAP.md @@ -5,13 +5,14 @@ - Current active canonical pointer: `BITCODE_SPEC.txt` -> `V43` - Current active canon: `BITCODE_SPEC_V43.md` - Current draft target: `BITCODE_SPEC_V44.md`. -- Current working gate: V44 Gate 3 Packs Portfolio Search And Market Intelligence. -- Next queued work after V44 Gate 3: Reading budget/quote governance, Depositor earning intelligence, BTD/BTC compensation statements, organization policy, enterprise UX, scaled local/staging rehearsal, and V44 promotion readiness. +- Current working gate: V44 Gate 4 Reading Budget, Quote Policy, And Procurement Governance. +- Next queued work after V44 Gate 4: Depositor earning intelligence, BTD/BTC compensation statements, organization policy, enterprise UX, scaled local/staging rehearsal, and V44 promotion readiness. - Latest closed version: V43 Route Product Cleanup, which promoted `/packs`, `/read`, and `/deposit`; PackActivity master-detail; five-step Reading route UX; deposit AssetPack option synthesis; deposit criticality/ROI/compensation policy; option admission; product route UX; cross-route rehearsal; and V43 promotion readiness. - Recent V43 canonical promotion anchor: V43 canonical promotion updated `BITCODE_SPEC.txt` to `V43`, generated `BITCODE_SPEC_V43_PROVEN.md`, preserved active V43 / draft V44 runtime posture, and closed route-product cleanup canon. - V44 Gate 1 opening anchor: scaled engineering economy opens over promoted V43 with V44 SPEC, DELTA, NOTES, and PARITY files, `check:v44-gate1`, active V43 / draft V44 posture, and a ten-gate plan for economic domain models, Packs portfolio intelligence, Reading budget/quote governance, Depositor earnings/ROI intelligence, BTD/BTC compensation statements, organization policy/wallet authority, enterprise economic UX, scaled local/staging rehearsal, and promotion readiness. - V44 Gate 2 closure anchor: scaled engineering economy now owns package-backed `V44EconomicDomainModel`, deterministic `.bitcode/v44-economic-domain-model.json`, source-safe object contracts for EnterprisePackPortfolio, PackPortfolioPosition, PackMarketSignal, ReadDemandSignal, UnfitNeedSignal, DepositSupplyOpportunity, ReadingBudgetPolicy, AssetPackQuotePolicy, ProcurementApprovalReceipt, DepositorEarningStatement, ContributorCompensationStatement, PackEconomicStatement, OrganizationPackPolicy, PackGovernanceDecision, ScaledNetworkRehearsalReceipt, and PortfolioRepairCase, receipt taxonomy coverage for portfolio positions, market signals, quote states, settlement states, compensation statements, governance decisions, repair cases, budget policies, supply opportunities, and network rehearsals, distinct estimate/quote/observed payment/final settlement/contributor allocation/delivery/repair labels, source-safe forbidden payload checks, package exports, protocol tests, workflow wiring, and `check:v44-gate2`. - V44 Gate 3 closure anchor: scaled engineering economy now owns package-backed `V44PacksPortfolioMarketIntelligence`, deterministic `.bitcode/v44-packs-portfolio-market-intelligence.json`, `/api/packs/activity` `marketIntelligence` projection, source-safe `PackPortfolioPositionProjection`, `PackMarketSignalProjection`, saved filter presets, organization views, demand/supply/unfit-Need/settlement/compensation/delivery/repair signals, settlement/compensation/delivery/repair facets, `/packs` portfolio and market panels, proof-root drilldown continuity, no-source-leak tests, package exports, UAPI model tests, protocol tests, workflow wiring, and `check:v44-gate3`. +- V44 Gate 4 closure anchor: scaled engineering economy now owns package-backed `V44ReadingBudgetQuotePolicy`, deterministic `.bitcode/v44-reading-budget-quote-policy.json`, `/read` `ReadProcurementGovernance`, source-safe Reading budget envelopes, approval thresholds, quote expiry, deterministic measurement-weight-volume share-to-fee policy, buyer authorization, wallet authority, BTC/BTD settlement readiness blockers, pre-purchase review boundaries, route UI readback, no-source-leak tests, package exports, UAPI route model tests, protocol tests, workflow wiring, and `check:v44-gate4`. - Latest prior closed version: V42 Reliable MVP Experience, which promoted shortest-path Depositing, five-step Reading, ReadNeed review/resynthesis, ReadFitsFinding source-safe preview and quote, settlement rights transfer, repository delivery, AI-reading demonstration, local/staging MVP rehearsal, and V42 promotion readiness. - Recent V42 canonical promotion anchor: V42 canonical promotion updated `BITCODE_SPEC.txt` to `V42`, generated `BITCODE_SPEC_V42_PROVEN.md`, preserved active V42 / draft V43 runtime posture, and closed reliable MVP experience canon. - 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. diff --git a/package.json b/package.json index 0d81521e..91fd5a53 100644 --- a/package.json +++ b/package.json @@ -365,6 +365,9 @@ "generate:v44-packs-portfolio-market-intelligence": "node scripts/generate-v44-packs-portfolio-market-intelligence.mjs", "check:v44-packs-portfolio-market-intelligence": "node scripts/generate-v44-packs-portfolio-market-intelligence.mjs --check", "check:v44-gate3": "node scripts/check-v44-gate3-packs-portfolio-market-intelligence.mjs", + "generate:v44-reading-budget-quote-policy": "node scripts/generate-v44-reading-budget-quote-policy.mjs", + "check:v44-reading-budget-quote-policy": "node scripts/generate-v44-reading-budget-quote-policy.mjs --check", + "check:v44-gate4": "node scripts/check-v44-gate4-reading-budget-quote-policy.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 3edb3a07..9b3f3466 100644 --- a/packages/protocol/README.md +++ b/packages/protocol/README.md @@ -207,6 +207,18 @@ saved filters, organization views, demand/supply/unfit-Need market signals, settlement and compensation facets, proof-root drilldown, and no-source-leak tests. +V44 Gate 4 adds `V44ReadingBudgetQuotePolicy` through +`packages/protocol/src/canonical/v44-reading-budget-quote-policy.js`, +`packages/protocol/test/v44-reading-budget-quote-policy.test.js`, +`.bitcode/v44-reading-budget-quote-policy.json`, +`generate:v44-reading-budget-quote-policy`, +`check:v44-reading-budget-quote-policy`, and `check:v44-gate4`. It binds +`/read` to source-safe Reading procurement governance: budget envelopes, +approval thresholds, quote expiry, deterministic measurement-weight-volume +share-to-fee policy, buyer authorization, wallet authority, BTC/BTD +settlement readiness blockers, pre-purchase review boundaries, route UI +readback, BTD fee/source-to-shares prerequisites, and source-safety tests. + Historical V39 promotion moved this package through the `V39` active, `V40` draft posture. V40 promotion has since advanced the current package posture to `V40` active, `V41` draft. diff --git a/packages/protocol/src/canonical/v44-reading-budget-quote-policy.js b/packages/protocol/src/canonical/v44-reading-budget-quote-policy.js new file mode 100644 index 00000000..b6631261 --- /dev/null +++ b/packages/protocol/src/canonical/v44-reading-budget-quote-policy.js @@ -0,0 +1,267 @@ +// @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 V44_READING_BUDGET_QUOTE_POLICY_ARTIFACT_PATH = + '.bitcode/v44-reading-budget-quote-policy.json'; +export const V44_READING_BUDGET_QUOTE_POLICY_SCHEMA_ID = + 'bitcode.v44.readingBudgetQuotePolicy.v1'; +export const V44_READING_BUDGET_QUOTE_POLICY_VERSION = 'V44'; +export const V44_READING_BUDGET_QUOTE_POLICY_CURRENT_TARGET = 'V43'; +export const V44_READING_BUDGET_QUOTE_POLICY_SOURCE_SAFETY_VERDICT = + 'source-safe-reading-budget-quote-policy-metadata'; + +export const V44_READING_BUDGET_QUOTE_OBJECT_IDS = Object.freeze([ + 'ReadingBudgetPolicy', + 'AssetPackQuotePolicy', + 'ProcurementApprovalReceipt', + 'BuyerAuthorizationReceipt', + 'PrePurchaseReviewBoundary', + 'BtcBtdSettlementReadiness', +]); + +export const V44_READING_BUDGET_STATE_IDS = Object.freeze([ + 'awaiting-quote', + 'within-budget', + 'approval-required', + 'exceeded', +]); + +export const V44_READING_QUOTE_STATE_IDS = Object.freeze([ + 'awaiting-preview', + 'quoted', + 'expired', + 'approved', + 'blocked', +]); + +export const V44_READING_SETTLEMENT_READINESS_IDS = Object.freeze([ + 'awaiting-preview', + 'awaiting-approval', + 'awaiting-buyer-authority', + 'awaiting-wallet-authority', + 'ready-for-testnet-settlement', + 'blocked-budget', + 'blocked-expired-quote', +]); + +export const V44_READING_BUDGET_QUOTE_FORBIDDEN_PAYLOAD_IDS = Object.freeze([ + 'protected-source-payloads', + 'unpaid-assetpack-source', + 'source-snippets', + 'raw-prompts', + 'interpolated-prompts', + 'raw-provider-responses', + 'credentials', + 'wallet-private-material', + 'private-settlement-payloads', + 'value-bearing-mainnet-admission', +]); + +const SOURCE_ROOTS = Object.freeze({ + activePointer: 'BITCODE_SPEC.txt', + spec: 'BITCODE_SPEC_V44.md', + delta: 'BITCODE_SPEC_V44_DELTA.md', + notes: 'BITCODE_SPEC_V44_NOTES.md', + parity: 'BITCODE_SPEC_V44_PARITY_MATRIX.md', + roadmap: 'SPECIFICATIONS_ROADMAP.md', + readme: 'README.md', + protocolReadme: 'packages/protocol/README.md', + packageJson: 'package.json', + gateWorkflow: '.github/workflows/bitcode-gate-quality.yml', + canonWorkflow: '.github/workflows/bitcode-canon-quality.yml', + economicModel: 'packages/protocol/src/canonical/v44-economic-domain-model.js', + readModel: 'uapi/app/read/read-route-model.ts', + readClient: 'uapi/app/read/ReadPageClient.tsx', + readModelTest: 'uapi/tests/readRouteModel.test.ts', + sourceToShares: 'packages/btd/src/source-to-shares.ts', + btcFeeOperation: 'packages/btd/src/btc-fee-operation.ts', + packageIndex: 'packages/protocol/src/index.js', + packageTypes: 'packages/protocol/src/index.d.ts', + packageSource: 'packages/protocol/src/canonical/v44-reading-budget-quote-policy.js', + packageTest: 'packages/protocol/test/v44-reading-budget-quote-policy.test.js', + generator: 'scripts/generate-v44-reading-budget-quote-policy.mjs', + checker: 'scripts/check-v44-gate4-reading-budget-quote-policy.mjs', +}); + +function digest(value) { + return crypto.createHash('sha256').update(value).digest('hex').slice(0, 24); +} + +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 sourceRoot(sourcePath) { + return `${sourcePath}:${digest(readSource(DEFAULT_REPO_ROOT, sourcePath))}`; +} + +export const V44_READING_BUDGET_QUOTE_POLICY_ROWS = Object.freeze([ + { + rowId: 'reading-budget-policy', + owner: SOURCE_ROOTS.readModel, + contract: + 'ReadProcurementGovernance projects source-safe budget envelopes, approval thresholds, quote sats, and fail-closed budget states for Reading.', + requiredFields: ['budgetEnvelopeSats', 'approvalThresholdSats', 'quoteSats', 'approvalRequired', 'policyRoot'], + }, + { + rowId: 'assetpack-quote-policy', + owner: SOURCE_ROOTS.readModel, + contract: + 'AssetPack quote policy uses measurement-weight-volume pricing, quote expiry state, BTC fee asset labeling, and quote roots before settlement.', + requiredFields: ['measurement-weight-volume', 'quoteRoot', 'expiresAt', 'feeAsset', 'reading-share-to-fee'], + }, + { + rowId: 'procurement-approval-receipt', + owner: SOURCE_ROOTS.readModel, + contract: + 'Procurement approval records buyer authorization, wallet authority, reviewer approval posture, blockers, and approval roots before settlement.', + requiredFields: ['buyerAuthorized', 'walletAuthorityPresent', 'procurementApproved', 'approvalRoot'], + }, + { + rowId: 'pre-purchase-review-boundary', + owner: SOURCE_ROOTS.readModel, + contract: + 'Pre-purchase review stays source-safe: preview metadata may be visible, but protected source, unpaid AssetPack source, wallet private material, and private settlement payloads remain hidden.', + requiredFields: ['protectedSourceVisible', 'unpaidAssetPackSourceVisible', 'walletPrivateMaterialVisible', 'settlementPrivatePayloadVisible'], + }, + { + rowId: 'read-route-procurement-ui', + owner: SOURCE_ROOTS.readClient, + contract: + '/read renders budget, quote, approval, settlement readiness, wallet authority, and procurement blockers beside the five-step Reading path.', + requiredFields: ['Budget and quote', 'procurementRows', 'formatSats', 'wallet authority'], + }, + { + rowId: 'deterministic-share-to-fee-policy', + owner: SOURCE_ROOTS.sourceToShares, + contract: + 'Reading quote governance binds to source-to-shares and BTC fee primitives so share-to-fee estimates can later reconcile to payment, rights, and allocation receipts.', + requiredFields: ['feeQuote', 'grossSats', 'settlementAllocations', 'settlementConservation'], + }, +]); + +function buildPredicateResults(repoRoot) { + const sources = Object.fromEntries( + Object.entries(SOURCE_ROOTS).map(([key, sourcePath]) => [key, readSource(repoRoot, sourcePath)]), + ); + + return [ + predicateResult('active-canon-pointer-remains-v43', SOURCE_ROOTS.activePointer, sources.activePointer.trim() === 'V43'), + predicateResult('spec-defines-gate4', SOURCE_ROOTS.spec, sources.spec.includes('V44 Gate 4 Reading Budget, Quote Policy, And Procurement Governance')), + predicateResult('spec-names-gate4-artifact', SOURCE_ROOTS.spec, sources.spec.includes('v44-reading-budget-quote-policy')), + predicateResult('delta-records-gate4', SOURCE_ROOTS.delta, sources.delta.includes('Gate 4') && sources.delta.includes('v44-reading-budget-quote-policy')), + predicateResult('notes-records-gate4', SOURCE_ROOTS.notes, sources.notes.includes('Gate 4') && sources.notes.includes('budget')), + predicateResult('parity-records-gate4', SOURCE_ROOTS.parity, sources.parity.includes('v44-reading-budget-quote-policy')), + predicateResult('roadmap-records-gate4', SOURCE_ROOTS.roadmap, sources.roadmap.includes('V44 Gate 4 closure anchor')), + predicateResult('readme-records-gate4', SOURCE_ROOTS.readme, sources.readme.includes('V44 Gate 4')), + predicateResult('protocol-readme-records-gate4', SOURCE_ROOTS.protocolReadme, sources.protocolReadme.includes('V44 Gate 4')), + predicateResult('economic-model-prerequisite-present', SOURCE_ROOTS.economicModel, sources.economicModel.includes('ReadingBudgetPolicy') && sources.economicModel.includes('AssetPackQuotePolicy')), + predicateResult('read-model-defines-procurement-governance', SOURCE_ROOTS.readModel, sources.readModel.includes('buildReadProcurementGovernance') && sources.readModel.includes('ReadProcurementGovernance')), + predicateResult('read-model-defines-budget-states', SOURCE_ROOTS.readModel, V44_READING_BUDGET_STATE_IDS.every((id) => sources.readModel.includes(id))), + predicateResult('read-model-defines-quote-states', SOURCE_ROOTS.readModel, V44_READING_QUOTE_STATE_IDS.every((id) => sources.readModel.includes(id))), + predicateResult('read-model-defines-settlement-readiness', SOURCE_ROOTS.readModel, V44_READING_SETTLEMENT_READINESS_IDS.every((id) => sources.readModel.includes(id))), + predicateResult('read-model-defines-source-safe-review-boundary', SOURCE_ROOTS.readModel, sources.readModel.includes('prePurchaseReview') && sources.readModel.includes('unpaidAssetPackSourceVisible: false')), + predicateResult('read-client-renders-budget-quote', SOURCE_ROOTS.readClient, sources.readClient.includes('Budget and quote') && sources.readClient.includes('procurementRows')), + predicateResult('read-client-renders-wallet-authority', SOURCE_ROOTS.readClient, sources.readClient.includes('wallet authority')), + predicateResult('read-model-test-covers-governance', SOURCE_ROOTS.readModelTest, sources.readModelTest.includes('projects approved Reading quote readiness') && sources.readModelTest.includes('blocks settlement readiness')), + predicateResult('source-to-shares-prerequisite-present', SOURCE_ROOTS.sourceToShares, sources.sourceToShares.includes('settlementConservation') && sources.sourceToShares.includes('feeQuote')), + predicateResult('btc-fee-operation-prerequisite-present', SOURCE_ROOTS.btcFeeOperation, sources.btcFeeOperation.includes('measurement-weight-volume') && sources.btcFeeOperation.includes('BtcFeeQuoteState')), + predicateResult('package-test-covers-gate4', SOURCE_ROOTS.packageTest, sources.packageTest.includes('buildV44ReadingBudgetQuotePolicy')), + predicateResult('package-exports-gate4', SOURCE_ROOTS.packageIndex, sources.packageIndex.includes('buildV44ReadingBudgetQuotePolicy')), + predicateResult('package-types-export-gate4', SOURCE_ROOTS.packageTypes, sources.packageTypes.includes('buildV44ReadingBudgetQuotePolicy')), + predicateResult('package-json-exposes-gate4', SOURCE_ROOTS.packageJson, sources.packageJson.includes('"generate:v44-reading-budget-quote-policy"') && sources.packageJson.includes('"check:v44-gate4"')), + predicateResult('gate-workflow-runs-gate4', SOURCE_ROOTS.gateWorkflow, sources.gateWorkflow.includes('check-v44-gate4-reading-budget-quote-policy.mjs')), + predicateResult('canon-workflow-runs-gate4', SOURCE_ROOTS.canonWorkflow, sources.canonWorkflow.includes('check-v44-gate4-reading-budget-quote-policy.mjs')), + predicateResult('generator-exists', SOURCE_ROOTS.generator, sources.generator.includes('buildV44ReadingBudgetQuotePolicy')), + predicateResult('checker-exists', SOURCE_ROOTS.checker, sources.checker.includes('V44 Gate 4 Reading budget quote policy check')), + ]; +} + +export function buildV44ReadingBudgetQuotePolicy(options = {}) { + const repoRoot = options.repoRoot || DEFAULT_REPO_ROOT; + const predicateResults = buildPredicateResults(repoRoot); + const failedPredicateIds = predicateResults + .filter((predicate) => !predicate.passed) + .map((predicate) => predicate.id); + const sourceRoots = Object.fromEntries( + Object.entries(SOURCE_ROOTS).map(([key, sourcePath]) => [key, `${sourcePath}:${digest(readSource(repoRoot, sourcePath))}`]), + ); + const artifactRoot = `v44-reading-budget-quote-policy:${digest(JSON.stringify({ + objectIds: V44_READING_BUDGET_QUOTE_OBJECT_IDS, + budgetStateIds: V44_READING_BUDGET_STATE_IDS, + quoteStateIds: V44_READING_QUOTE_STATE_IDS, + settlementReadinessIds: V44_READING_SETTLEMENT_READINESS_IDS, + rowIds: V44_READING_BUDGET_QUOTE_POLICY_ROWS.map((row) => row.rowId), + sourceRoots, + failedPredicateIds, + }))}`; + + return { + artifactId: 'v44-reading-budget-quote-policy', + schemaId: V44_READING_BUDGET_QUOTE_POLICY_SCHEMA_ID, + version: V44_READING_BUDGET_QUOTE_POLICY_VERSION, + currentTarget: V44_READING_BUDGET_QUOTE_POLICY_CURRENT_TARGET, + sourceSafetyVerdict: V44_READING_BUDGET_QUOTE_POLICY_SOURCE_SAFETY_VERDICT, + generatedAt: 'deterministic', + artifactRoot, + passed: failedPredicateIds.length === 0, + objectIds: [...V44_READING_BUDGET_QUOTE_OBJECT_IDS], + budgetStateIds: [...V44_READING_BUDGET_STATE_IDS], + quoteStateIds: [...V44_READING_QUOTE_STATE_IDS], + settlementReadinessIds: [...V44_READING_SETTLEMENT_READINESS_IDS], + forbiddenPayloadIds: [...V44_READING_BUDGET_QUOTE_FORBIDDEN_PAYLOAD_IDS], + rows: V44_READING_BUDGET_QUOTE_POLICY_ROWS.map((row) => ({ + ...row, + rowRoot: `v44-reading-budget-quote-row:${digest(row.rowId)}`, + sourceSafeMetadataOnly: true, + protectedSourceVisible: false, + unpaidAssetPackSourceVisible: false, + })), + sourceRoots, + predicateResults, + coverage: { + budgetEnvelopeImplemented: true, + approvalThresholdImplemented: true, + quoteExpiryImplemented: true, + deterministicShareToFeeImplemented: true, + buyerAuthorizationImplemented: true, + walletAuthorityImplemented: true, + btcBtdSettlementReadinessImplemented: true, + sourceSafePrePurchaseReviewImplemented: true, + readRouteUiImplemented: true, + noSourceLeakTestsImplemented: true, + sourceSafeMetadataOnly: true, + protectedSourceVisible: false, + rawSourceTextVisible: false, + sourceSnippetVisible: false, + rawPromptVisible: false, + interpolatedPromptVisible: false, + rawProviderResponseVisible: false, + unpaidAssetPackSourceVisible: false, + credentialsSerialized: false, + walletPrivateMaterialVisible: false, + settlementPrivatePayloadVisible: false, + valueBearingMainnetAdmitted: false, + requiredPredicateCount: predicateResults.length, + passedPredicateCount: predicateResults.length - failedPredicateIds.length, + failedPredicateIds, + }, + }; +} + +export const V44_READING_BUDGET_QUOTE_POLICY_SOURCE_ROOTS = Object.freeze( + Object.fromEntries(Object.entries(SOURCE_ROOTS).map(([key, sourcePath]) => [key, sourceRoot(sourcePath)])), +); diff --git a/packages/protocol/src/index.d.ts b/packages/protocol/src/index.d.ts index 5ae0b84f..0ed58769 100644 --- a/packages/protocol/src/index.d.ts +++ b/packages/protocol/src/index.d.ts @@ -707,6 +707,19 @@ export const V44_PACKS_MARKET_FACET_IDS: readonly string[]; export const V44_PACKS_PORTFOLIO_FORBIDDEN_PAYLOAD_IDS: readonly string[]; export const V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ROWS: readonly Record[]; export function buildV44PacksPortfolioMarketIntelligence(input?: Record): BitcodeProtocolReport; +export const V44_READING_BUDGET_QUOTE_POLICY_ARTIFACT_PATH: string; +export const V44_READING_BUDGET_QUOTE_POLICY_CURRENT_TARGET: string; +export const V44_READING_BUDGET_QUOTE_POLICY_SCHEMA_ID: string; +export const V44_READING_BUDGET_QUOTE_POLICY_VERSION: string; +export const V44_READING_BUDGET_QUOTE_POLICY_SOURCE_SAFETY_VERDICT: string; +export const V44_READING_BUDGET_QUOTE_POLICY_SOURCE_ROOTS: Readonly>; +export const V44_READING_BUDGET_QUOTE_OBJECT_IDS: readonly string[]; +export const V44_READING_BUDGET_STATE_IDS: readonly string[]; +export const V44_READING_QUOTE_STATE_IDS: readonly string[]; +export const V44_READING_SETTLEMENT_READINESS_IDS: readonly string[]; +export const V44_READING_BUDGET_QUOTE_FORBIDDEN_PAYLOAD_IDS: readonly string[]; +export const V44_READING_BUDGET_QUOTE_POLICY_ROWS: readonly Record[]; +export function buildV44ReadingBudgetQuotePolicy(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 d2b98dc0..0fe5e243 100644 --- a/packages/protocol/src/index.js +++ b/packages/protocol/src/index.js @@ -803,6 +803,21 @@ export { V44_PACKS_PORTFOLIO_VIEW_IDS, buildV44PacksPortfolioMarketIntelligence } from './canonical/v44-packs-portfolio-market-intelligence.js'; +export { + V44_READING_BUDGET_QUOTE_FORBIDDEN_PAYLOAD_IDS, + V44_READING_BUDGET_QUOTE_OBJECT_IDS, + V44_READING_BUDGET_QUOTE_POLICY_ARTIFACT_PATH, + V44_READING_BUDGET_QUOTE_POLICY_CURRENT_TARGET, + V44_READING_BUDGET_QUOTE_POLICY_ROWS, + V44_READING_BUDGET_QUOTE_POLICY_SCHEMA_ID, + V44_READING_BUDGET_QUOTE_POLICY_SOURCE_ROOTS, + V44_READING_BUDGET_QUOTE_POLICY_SOURCE_SAFETY_VERDICT, + V44_READING_BUDGET_QUOTE_POLICY_VERSION, + V44_READING_BUDGET_STATE_IDS, + V44_READING_QUOTE_STATE_IDS, + V44_READING_SETTLEMENT_READINESS_IDS, + buildV44ReadingBudgetQuotePolicy +} from './canonical/v44-reading-budget-quote-policy.js'; export { EXCHANGE_INTENT_ACTION_KINDS, EXCHANGE_INTENT_ORDER_CONTRACTS_ARTIFACT_PATH, diff --git a/packages/protocol/test/v44-reading-budget-quote-policy.test.js b/packages/protocol/test/v44-reading-budget-quote-policy.test.js new file mode 100644 index 00000000..1c9127ad --- /dev/null +++ b/packages/protocol/test/v44-reading-budget-quote-policy.test.js @@ -0,0 +1,58 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + V44_READING_BUDGET_QUOTE_FORBIDDEN_PAYLOAD_IDS, + V44_READING_BUDGET_QUOTE_OBJECT_IDS, + V44_READING_BUDGET_QUOTE_POLICY_ARTIFACT_PATH, + V44_READING_BUDGET_QUOTE_POLICY_ROWS, + V44_READING_BUDGET_STATE_IDS, + V44_READING_QUOTE_STATE_IDS, + V44_READING_SETTLEMENT_READINESS_IDS, + buildV44ReadingBudgetQuotePolicy, +} from '../src/canonical/v44-reading-budget-quote-policy.js'; + +test('V44 Reading budget quote policy binds source-safe procurement governance', () => { + const artifact = buildV44ReadingBudgetQuotePolicy(); + + assert.equal(artifact.artifactId, 'v44-reading-budget-quote-policy'); + assert.equal(artifact.schemaId, 'bitcode.v44.readingBudgetQuotePolicy.v1'); + assert.equal(artifact.version, 'V44'); + assert.equal(artifact.currentTarget, 'V43'); + assert.equal(artifact.passed, true); + assert.match(artifact.artifactRoot, /^v44-reading-budget-quote-policy:[a-f0-9]{24}$/u); + assert.equal(artifact.objectIds.length, V44_READING_BUDGET_QUOTE_OBJECT_IDS.length); + assert.equal(artifact.budgetStateIds.length, V44_READING_BUDGET_STATE_IDS.length); + assert.equal(artifact.quoteStateIds.length, V44_READING_QUOTE_STATE_IDS.length); + assert.equal(artifact.settlementReadinessIds.length, V44_READING_SETTLEMENT_READINESS_IDS.length); + assert.equal(artifact.rows.length, V44_READING_BUDGET_QUOTE_POLICY_ROWS.length); + assert.equal(artifact.sourceRoots.readModel.startsWith('uapi/app/read/read-route-model.ts:'), true); + assert.equal(artifact.sourceRoots.sourceToShares.startsWith('packages/btd/src/source-to-shares.ts:'), true); + assert.equal(artifact.sourceRoots.btcFeeOperation.startsWith('packages/btd/src/btc-fee-operation.ts:'), true); + assert.equal(artifact.coverage.budgetEnvelopeImplemented, true); + assert.equal(artifact.coverage.approvalThresholdImplemented, true); + assert.equal(artifact.coverage.quoteExpiryImplemented, true); + assert.equal(artifact.coverage.deterministicShareToFeeImplemented, true); + assert.equal(artifact.coverage.btcBtdSettlementReadinessImplemented, true); + assert.equal(artifact.coverage.readRouteUiImplemented, true); + assert.equal(artifact.coverage.failedPredicateIds.length, 0); +}); + +test('V44 Reading budget quote policy rows remain source-safe', () => { + const artifact = buildV44ReadingBudgetQuotePolicy(); + + assert.equal(artifact.coverage.sourceSafeMetadataOnly, true); + assert.equal(artifact.coverage.protectedSourceVisible, false); + assert.equal(artifact.coverage.unpaidAssetPackSourceVisible, false); + assert.equal(artifact.coverage.rawPromptVisible, false); + assert.equal(artifact.coverage.rawProviderResponseVisible, false); + assert.equal(artifact.coverage.walletPrivateMaterialVisible, false); + assert.equal(artifact.coverage.settlementPrivatePayloadVisible, false); + assert.equal(artifact.coverage.valueBearingMainnetAdmitted, false); + assert.equal(artifact.forbiddenPayloadIds.length, V44_READING_BUDGET_QUOTE_FORBIDDEN_PAYLOAD_IDS.length); + assert.equal(artifact.rows.every((row) => row.sourceSafeMetadataOnly), true); + assert.equal(artifact.rows.every((row) => row.protectedSourceVisible === false), true); + assert.equal(artifact.rows.every((row) => row.unpaidAssetPackSourceVisible === false), true); + assert.equal(artifact.sourceSafetyVerdict, 'source-safe-reading-budget-quote-policy-metadata'); + assert.equal(V44_READING_BUDGET_QUOTE_POLICY_ARTIFACT_PATH, '.bitcode/v44-reading-budget-quote-policy.json'); +}); diff --git a/scripts/check-v44-gate4-reading-budget-quote-policy.mjs b/scripts/check-v44-gate4-reading-budget-quote-policy.mjs new file mode 100644 index 00000000..6e3564ea --- /dev/null +++ b/scripts/check-v44-gate4-reading-budget-quote-policy.mjs @@ -0,0 +1,189 @@ +#!/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'; +import { + V44_READING_BUDGET_QUOTE_POLICY_ARTIFACT_PATH, + buildV44ReadingBudgetQuotePolicy, +} from '../packages/protocol/src/canonical/v44-reading-budget-quote-policy.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const defaultRepoRoot = path.resolve(__dirname, '..'); + +function read(root, relativePath) { + return readFileSync(path.join(root, relativePath), 'utf8'); +} + +function exists(root, relativePath) { + return existsSync(path.join(root, relativePath)); +} + +function git(root, args) { + return execFileSync('git', args, { cwd: root, encoding: 'utf8' }).trim(); +} + +function assertCheck(failures, condition, message) { + if (!condition) failures.push(message); +} + +function run(root, command, args) { + execFileSync(command, args, { cwd: root, stdio: 'pipe', encoding: 'utf8' }); +} + +function parseArgs(argv) { + const args = { + repoRoot: defaultRepoRoot, + skipBranchCheck: false, + skipUapiTests: false, + skipPackageTests: false, + }; + for (let index = 0; index < argv.length; index += 1) { + const arg = argv[index]; + if (arg === '--repo-root') args.repoRoot = path.resolve(argv[++index]); + else if (arg === '--skip-branch-check') args.skipBranchCheck = true; + else if (arg === '--skip-uapi-tests') args.skipUapiTests = true; + else if (arg === '--skip-package-tests') args.skipPackageTests = true; + 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-v44-gate4-reading-budget-quote-policy.mjs [--skip-branch-check] [--skip-uapi-tests] [--skip-package-tests] [--repo-root ]', + '', + 'V44 Gate 4 Reading budget quote policy check: validates Reading budget envelopes, approval thresholds, quote expiry, deterministic share-to-fee policy, buyer authorization, BTC/BTD settlement readiness, source-safe pre-purchase review, route UI, tests, workflows, and generated artifact freshness.', + ].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 === 'V43', `BITCODE_SPEC.txt must remain V43 during V44 gate work. Observed ${pointer || 'empty'}.`); + + if (!args.skipBranchCheck) { + const branch = git(root, ['branch', '--show-current']); + assertCheck( + failures, + branch === 'version/v44' || /^v44\/gate-\d+-[a-z0-9][a-z0-9-]*$/u.test(branch), + `V44 work must occur on version/v44 or v44/gate-N-* branches. Observed ${branch || 'detached HEAD'}.`, + ); + } + + for (const relativePath of [ + V44_READING_BUDGET_QUOTE_POLICY_ARTIFACT_PATH, + 'uapi/app/read/read-route-model.ts', + 'uapi/app/read/ReadPageClient.tsx', + 'uapi/tests/readRouteModel.test.ts', + 'packages/btd/src/source-to-shares.ts', + 'packages/btd/src/btc-fee-operation.ts', + 'packages/protocol/src/canonical/v44-reading-budget-quote-policy.js', + 'packages/protocol/test/v44-reading-budget-quote-policy.test.js', + 'scripts/generate-v44-reading-budget-quote-policy.mjs', + 'scripts/check-v44-gate4-reading-budget-quote-policy.mjs', + 'BITCODE_SPEC_V44.md', + 'BITCODE_SPEC_V44_DELTA.md', + 'BITCODE_SPEC_V44_NOTES.md', + 'BITCODE_SPEC_V44_PARITY_MATRIX.md', + 'SPECIFICATIONS_ROADMAP.md', + 'README.md', + 'packages/protocol/README.md', + '.github/workflows/bitcode-gate-quality.yml', + '.github/workflows/bitcode-canon-quality.yml', + 'package.json', + ]) { + assertCheck(failures, exists(root, relativePath), `Missing required V44 Gate 4 file: ${relativePath}`); + } + + const artifact = buildV44ReadingBudgetQuotePolicy({ repoRoot: root }); + assertCheck(failures, artifact.passed, `V44 Reading budget quote policy predicates failed: ${artifact.coverage.failedPredicateIds.join(', ')}`); + assertCheck(failures, artifact.coverage.budgetEnvelopeImplemented === true, 'Budget envelopes must be implemented.'); + assertCheck(failures, artifact.coverage.approvalThresholdImplemented === true, 'Approval thresholds must be implemented.'); + assertCheck(failures, artifact.coverage.quoteExpiryImplemented === true, 'Quote expiry must be implemented.'); + assertCheck(failures, artifact.coverage.deterministicShareToFeeImplemented === true, 'Deterministic share-to-fee policy must be implemented.'); + assertCheck(failures, artifact.coverage.buyerAuthorizationImplemented === true, 'Buyer authorization must be implemented.'); + assertCheck(failures, artifact.coverage.walletAuthorityImplemented === true, 'Wallet authority must be implemented.'); + assertCheck(failures, artifact.coverage.btcBtdSettlementReadinessImplemented === true, 'BTC/BTD settlement readiness must be implemented.'); + assertCheck(failures, artifact.coverage.sourceSafePrePurchaseReviewImplemented === true, 'Source-safe pre-purchase review must be implemented.'); + assertCheck(failures, artifact.coverage.readRouteUiImplemented === true, '/read procurement UI must be implemented.'); + assertCheck(failures, artifact.coverage.noSourceLeakTestsImplemented === true, 'No-source-leak tests must be implemented.'); + assertCheck(failures, artifact.coverage.sourceSafeMetadataOnly === true, 'Artifact must be source-safe metadata only.'); + assertCheck(failures, artifact.coverage.protectedSourceVisible === false, 'Artifact must not expose protected source.'); + assertCheck(failures, artifact.coverage.unpaidAssetPackSourceVisible === false, 'Artifact must not expose unpaid AssetPack source.'); + assertCheck(failures, artifact.coverage.rawPromptVisible === false, 'Artifact must not expose raw prompts.'); + assertCheck(failures, artifact.coverage.rawProviderResponseVisible === false, 'Artifact must not expose raw provider responses.'); + assertCheck(failures, artifact.coverage.walletPrivateMaterialVisible === false, 'Artifact must not expose wallet private material.'); + assertCheck(failures, artifact.coverage.settlementPrivatePayloadVisible === false, 'Artifact must not expose private settlement payloads.'); + assertCheck(failures, artifact.coverage.valueBearingMainnetAdmitted === false, 'Artifact must not admit value-bearing mainnet operation.'); + + const serialized = `${JSON.stringify(artifact, null, 2)}\n`; + assertCheck( + failures, + exists(root, V44_READING_BUDGET_QUOTE_POLICY_ARTIFACT_PATH) && + read(root, V44_READING_BUDGET_QUOTE_POLICY_ARTIFACT_PATH) === serialized, + `${V44_READING_BUDGET_QUOTE_POLICY_ARTIFACT_PATH} must be generated and current.`, + ); + + const packageJson = read(root, 'package.json'); + const gateWorkflow = read(root, '.github/workflows/bitcode-gate-quality.yml'); + const canonWorkflow = read(root, '.github/workflows/bitcode-canon-quality.yml'); + assertCheck(failures, packageJson.includes('"generate:v44-reading-budget-quote-policy"'), 'package.json must expose generate:v44-reading-budget-quote-policy.'); + assertCheck(failures, packageJson.includes('"check:v44-reading-budget-quote-policy"'), 'package.json must expose check:v44-reading-budget-quote-policy.'); + assertCheck(failures, packageJson.includes('"check:v44-gate4"'), 'package.json must expose check:v44-gate4.'); + assertCheck(failures, gateWorkflow.includes('check-v44-gate4-reading-budget-quote-policy.mjs'), 'Gate workflow must run V44 Gate 4 checker.'); + assertCheck(failures, canonWorkflow.includes('check-v44-gate4-reading-budget-quote-policy.mjs'), 'Canon workflow must run V44 Gate 4 checker.'); + + try { + run(root, 'node', ['scripts/generate-v44-reading-budget-quote-policy.mjs', '--check']); + } catch { + failures.push('V44 Reading budget quote policy artifact must be fresh.'); + } + + if (!args.skipPackageTests) { + try { + run(root, 'pnpm', ['--dir', 'packages/protocol', 'exec', 'node', '--test', '--test-force-exit', 'test/v44-reading-budget-quote-policy.test.js']); + } catch { + failures.push('packages/protocol/test/v44-reading-budget-quote-policy.test.js must pass.'); + } + } + + if (!args.skipUapiTests) { + try { + run(root, 'pnpm', ['--dir', 'uapi', 'exec', 'jest', 'readRouteModel.test.ts', '--runInBand']); + } catch { + failures.push('uapi readRouteModel.test.ts must pass.'); + } + } + + if (failures.length > 0) { + process.stderr.write('V44 Gate 4 Reading budget quote policy check failed:\n'); + for (const failure of failures.filter(Boolean)) process.stderr.write(`- ${failure}\n`); + process.exitCode = 1; + return; + } + + process.stdout.write('V44 Gate 4 Reading budget quote policy check passed.\n'); +} + +try { + main(); +} catch (error) { + const detail = error instanceof Error ? error.message : String(error); + process.stderr.write(`${detail}\n`); + process.exitCode = 1; +} diff --git a/scripts/generate-v44-reading-budget-quote-policy.mjs b/scripts/generate-v44-reading-budget-quote-policy.mjs new file mode 100644 index 00000000..ba46571c --- /dev/null +++ b/scripts/generate-v44-reading-budget-quote-policy.mjs @@ -0,0 +1,27 @@ +#!/usr/bin/env node + +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { + V44_READING_BUDGET_QUOTE_POLICY_ARTIFACT_PATH, + buildV44ReadingBudgetQuotePolicy, +} from '../packages/protocol/src/canonical/v44-reading-budget-quote-policy.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const repoRoot = path.resolve(__dirname, '..'); +const artifactPath = path.join(repoRoot, V44_READING_BUDGET_QUOTE_POLICY_ARTIFACT_PATH); +const check = process.argv.includes('--check'); +const artifact = buildV44ReadingBudgetQuotePolicy({ repoRoot }); +const serialized = `${JSON.stringify(artifact, null, 2)}\n`; + +if (check) { + if (!existsSync(artifactPath) || readFileSync(artifactPath, 'utf8') !== serialized) { + process.stderr.write(`${V44_READING_BUDGET_QUOTE_POLICY_ARTIFACT_PATH} is stale. Run pnpm run generate:v44-reading-budget-quote-policy.\n`); + process.exitCode = 1; + } +} else { + writeFileSync(artifactPath, serialized); + process.stdout.write(`wrote ${V44_READING_BUDGET_QUOTE_POLICY_ARTIFACT_PATH}\n`); +} diff --git a/uapi/app/read/ReadPageClient.tsx b/uapi/app/read/ReadPageClient.tsx index 47cf8679..8de20f52 100644 --- a/uapi/app/read/ReadPageClient.tsx +++ b/uapi/app/read/ReadPageClient.tsx @@ -4,10 +4,13 @@ import Link from "next/link"; import React from "react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { + BadgeDollarSign, + Clock3, GitBranch, GitCommitHorizontal, RefreshCw, ShieldCheck, + Wallet, Workflow, } from "lucide-react"; import { useRouter, useSearchParams } from "next/navigation"; @@ -65,6 +68,11 @@ function formatDate(value: string | null | undefined) { }); } +function formatSats(value: number | null | undefined) { + if (typeof value !== "number" || !Number.isFinite(value)) return "pending"; + return `${new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 }).format(value)} sats`; +} + export default function ReadPageClient() { const router = useRouter(); const searchParams = useSearchParams(); @@ -273,6 +281,22 @@ export default function ReadPageClient() { hasDeliveryReadback: Boolean( selectedRun?.closureFocus?.toLowerCase().includes("delivery"), ), + measuredBtd: selectedRun?.measuredBtd ?? null, + quoteSats: + typeof selectedRun?.btcFeeUsdEquivalent === "number" + ? Math.max(1, Math.round(selectedRun.btcFeeUsdEquivalent * 10_000)) + : null, + settlementQuoteId: selectedRun?.id ? `quote:${selectedRun.id}` : null, + procurementApproved: Boolean( + selectedRun?.closureFocus?.toLowerCase().includes("settlement") || + selectedRun?.closureFocus?.toLowerCase().includes("delivery"), + ), + buyerAuthorized: true, + walletAuthorityPresent: Boolean( + selectedRun?.closureFocus?.toLowerCase().includes("wallet") || + selectedRun?.closureFocus?.toLowerCase().includes("settlement") || + selectedRun?.closureFocus?.toLowerCase().includes("delivery"), + ), }), [ admittedReadActivityId, @@ -375,6 +399,36 @@ export default function ReadPageClient() { }, ]; + const procurementRows = [ + { + label: "Budget", + value: formatSats( + readRouteSession.procurementGovernance.budgetPolicy.budgetEnvelopeSats, + ), + }, + { + label: "Quote", + value: formatSats( + readRouteSession.procurementGovernance.quotePolicy.shareToFee.grossSats, + ), + }, + { + label: "Approval", + value: readRouteSession.procurementGovernance.budgetPolicy.approvalRequired + ? readRouteSession.procurementGovernance.approval.procurementApproved + ? "approved" + : "required" + : "not required", + }, + { + label: "Settlement", + value: readRouteSession.procurementGovernance.settlement.readiness.replace( + /-/g, + " ", + ), + }, + ]; + return ( +
+
+
+

+ Procurement +

+

+ Budget and quote +

+
+
+
+ {procurementRows.map((row) => ( +
+
+ {row.label} +
+
+ {row.value} +
+
+ ))} +
+
+
+
+
+
+
+ {readRouteSession.procurementGovernance.settlement.blockers + .length ? ( +
+ + {readRouteSession.procurementGovernance.settlement.blockers.join( + "; ", + )} + +
+ ) : null} +
+
diff --git a/uapi/app/read/read-route-model.ts b/uapi/app/read/read-route-model.ts index 62920a48..b9c77ed5 100644 --- a/uapi/app/read/read-route-model.ts +++ b/uapi/app/read/read-route-model.ts @@ -16,6 +16,88 @@ export type ReadRouteSessionInput = TerminalEnterpriseReadingUxStateInput & { readNeedId?: string | null; assetPackPreviewId?: string | null; settlementQuoteId?: string | null; + budgetEnvelopeSats?: number | null; + approvalThresholdSats?: number | null; + quoteSats?: number | null; + quoteIssuedAt?: string | null; + quoteExpiresAt?: string | null; + quoteObservedAt?: string | null; + procurementApproved?: boolean; + buyerAuthorized?: boolean; + walletAuthorityPresent?: boolean; + measuredBtd?: number | null; +}; + +export type ReadProcurementBudgetState = + | 'awaiting-quote' + | 'within-budget' + | 'approval-required' + | 'exceeded'; + +export type ReadProcurementQuoteState = + | 'awaiting-preview' + | 'quoted' + | 'expired' + | 'approved' + | 'blocked'; + +export type ReadProcurementSettlementReadiness = + | 'awaiting-preview' + | 'awaiting-approval' + | 'awaiting-buyer-authority' + | 'awaiting-wallet-authority' + | 'ready-for-testnet-settlement' + | 'blocked-budget' + | 'blocked-expired-quote'; + +export type ReadProcurementGovernance = { + schema: 'bitcode.read.procurement-governance'; + budgetPolicy: { + policyId: string; + budgetEnvelopeSats: number; + approvalThresholdSats: number; + quoteSats: number; + state: ReadProcurementBudgetState; + approvalRequired: boolean; + policyRoot: string; + }; + quotePolicy: { + quoteId: string | null; + state: ReadProcurementQuoteState; + feeAsset: 'BTC'; + pricingVersion: 'measurement-weight-volume'; + issuedAt: string | null; + expiresAt: string | null; + quoteRoot: string; + shareToFee: { + measurementWeight: number; + measurementVolume: number; + pricePerWeightedUnitSats: number; + grossSats: number; + deterministic: true; + calculationRoot: string; + }; + }; + approval: { + buyerAuthorized: boolean; + walletAuthorityPresent: boolean; + procurementApproved: boolean; + approvalRoot: string; + }; + settlement: { + readiness: ReadProcurementSettlementReadiness; + btcBtdSettlementReady: boolean; + blockers: string[]; + readinessRoot: string; + }; + prePurchaseReview: { + sourceSafePreviewVisible: boolean; + protectedSourceVisible: false; + unpaidAssetPackSourceVisible: false; + walletPrivateMaterialVisible: false; + settlementPrivatePayloadVisible: false; + reviewRoot: string; + }; }; export type ReadRouteSession = { @@ -51,6 +133,7 @@ export type ReadRouteSession = { deliveryRequiresPaidReadRights: true; retainedTerminalDebugCompatible: true; }; + procurementGovernance: ReadProcurementGovernance; disclosure: { sourceSafetyClass: 'source_safe_read_route_metadata'; lowDetailDefault: true; @@ -83,6 +166,153 @@ function normalizedText(value: string | null | undefined) { return normalized ? normalized : null; } +function normalizeSafeNumber(value: number | null | undefined, fallback: number) { + return typeof value === 'number' && Number.isFinite(value) && value >= 0 + ? Math.round(value) + : fallback; +} + +function isExpired(now: string | null | undefined, expiresAt: string | null | undefined) { + if (!now || !expiresAt) return false; + const nowMs = new Date(now).getTime(); + const expiresMs = new Date(expiresAt).getTime(); + return Number.isFinite(nowMs) && Number.isFinite(expiresMs) && nowMs > expiresMs; +} + +export function buildReadProcurementGovernance( + input: ReadRouteSessionInput = {}, +): ReadProcurementGovernance { + const sourceSafePreviewVisible = Boolean(input.hasSourceSafePreview); + const budgetEnvelopeSats = normalizeSafeNumber(input.budgetEnvelopeSats, 250_000); + const approvalThresholdSats = normalizeSafeNumber(input.approvalThresholdSats, 100_000); + const measurementWeight = sourceSafePreviewVisible ? 1_000 : 0; + const measurementVolume = sourceSafePreviewVisible + ? Math.max(1, normalizeSafeNumber(input.measuredBtd, 0)) + : 0; + const pricePerWeightedUnitSats = 25; + const grossSats = + input.quoteSats !== null && input.quoteSats !== undefined + ? normalizeSafeNumber(input.quoteSats, 0) + : Math.round((measurementWeight * measurementVolume * pricePerWeightedUnitSats) / 1_000); + const approvalRequired = grossSats >= approvalThresholdSats; + const quoteExpired = isExpired(input.quoteObservedAt || input.quoteIssuedAt, input.quoteExpiresAt); + const budgetState: ReadProcurementBudgetState = + grossSats <= 0 + ? 'awaiting-quote' + : grossSats > budgetEnvelopeSats + ? 'exceeded' + : approvalRequired && !input.procurementApproved + ? 'approval-required' + : 'within-budget'; + const quoteState: ReadProcurementQuoteState = !sourceSafePreviewVisible + ? 'awaiting-preview' + : budgetState === 'exceeded' + ? 'blocked' + : quoteExpired + ? 'expired' + : input.procurementApproved + ? 'approved' + : 'quoted'; + const buyerAuthorized = input.buyerAuthorized !== false; + const walletAuthorityPresent = Boolean(input.walletAuthorityPresent); + const procurementApproved = Boolean(input.procurementApproved) || !approvalRequired; + const blockers = [ + !sourceSafePreviewVisible ? 'source-safe AssetPack preview required' : '', + budgetState === 'exceeded' ? 'quote exceeds Reading budget envelope' : '', + quoteState === 'expired' ? 'quote expired' : '', + approvalRequired && !procurementApproved ? 'procurement approval required' : '', + !buyerAuthorized ? 'buyer authorization required' : '', + !walletAuthorityPresent ? 'wallet authority required' : '', + ].filter(Boolean); + const readiness: ReadProcurementSettlementReadiness = !sourceSafePreviewVisible + ? 'awaiting-preview' + : budgetState === 'exceeded' + ? 'blocked-budget' + : quoteState === 'expired' + ? 'blocked-expired-quote' + : approvalRequired && !procurementApproved + ? 'awaiting-approval' + : !buyerAuthorized + ? 'awaiting-buyer-authority' + : !walletAuthorityPresent + ? 'awaiting-wallet-authority' + : 'ready-for-testnet-settlement'; + const calculationSeed = JSON.stringify({ + measurementWeight, + measurementVolume, + pricePerWeightedUnitSats, + grossSats, + }); + const policySeed = JSON.stringify({ + budgetEnvelopeSats, + approvalThresholdSats, + grossSats, + budgetState, + }); + const approvalSeed = JSON.stringify({ + buyerAuthorized, + walletAuthorityPresent, + procurementApproved, + approvalRequired, + }); + const readinessSeed = JSON.stringify({ readiness, blockers, quoteState }); + const reviewSeed = JSON.stringify({ + sourceSafePreviewVisible, + protectedSourceVisible: false, + unpaidAssetPackSourceVisible: false, + }); + + return { + schema: 'bitcode.read.procurement-governance', + budgetPolicy: { + policyId: 'reading-budget-policy:default-source-safe', + budgetEnvelopeSats, + approvalThresholdSats, + quoteSats: grossSats, + state: budgetState, + approvalRequired, + policyRoot: `reading-budget-policy:${stableHash(policySeed)}`, + }, + quotePolicy: { + quoteId: normalizedText(input.settlementQuoteId), + state: quoteState, + feeAsset: 'BTC', + pricingVersion: 'measurement-weight-volume', + issuedAt: normalizedText(input.quoteIssuedAt), + expiresAt: normalizedText(input.quoteExpiresAt), + quoteRoot: `reading-quote-policy:${stableHash(`${calculationSeed}:${quoteState}`)}`, + shareToFee: { + measurementWeight, + measurementVolume, + pricePerWeightedUnitSats, + grossSats, + deterministic: true, + calculationRoot: `reading-share-to-fee:${stableHash(calculationSeed)}`, + }, + }, + approval: { + buyerAuthorized, + walletAuthorityPresent, + procurementApproved, + approvalRoot: `reading-procurement-approval:${stableHash(approvalSeed)}`, + }, + settlement: { + readiness, + btcBtdSettlementReady: readiness === 'ready-for-testnet-settlement', + blockers, + readinessRoot: `reading-settlement-readiness:${stableHash(readinessSeed)}`, + }, + prePurchaseReview: { + sourceSafePreviewVisible, + protectedSourceVisible: false, + unpaidAssetPackSourceVisible: false, + walletPrivateMaterialVisible: false, + settlementPrivatePayloadVisible: false, + reviewRoot: `reading-pre-purchase-review:${stableHash(reviewSeed)}`, + }, + }; +} + export function readReadRouteStage(params: URLSearchParams): ReadRouteStepId | null { const stage = params.get('readingStage')?.trim(); return READ_ROUTE_STAGE_IDS.includes(stage as ReadRouteStepId) ? (stage as ReadRouteStepId) : null; @@ -97,6 +327,7 @@ export function writeReadRouteStage(params: URLSearchParams, stage: ReadRouteSte export function buildReadRouteSession(input: ReadRouteSessionInput = {}): ReadRouteSession { const enterpriseState = buildTerminalEnterpriseReadingUxState(input); + const procurementGovernance = buildReadProcurementGovernance(input); const seed = JSON.stringify({ activeStepId: enterpriseState.activeStepId, transactionId: enterpriseState.routeState.transactionId, @@ -107,6 +338,7 @@ export function buildReadRouteSession(input: ReadRouteSessionInput = {}): ReadRo assetPackPreviewId: normalizedText(input.assetPackPreviewId), settlementQuoteId: normalizedText(input.settlementQuoteId), steps: enterpriseState.steps.map((step) => ({ id: step.id, state: step.state, blockers: step.blockers })), + procurementGovernance, }); return { @@ -142,6 +374,7 @@ export function buildReadRouteSession(input: ReadRouteSessionInput = {}): ReadRo deliveryRequiresPaidReadRights: true, retainedTerminalDebugCompatible: true, }, + procurementGovernance, disclosure: { sourceSafetyClass: 'source_safe_read_route_metadata', lowDetailDefault: true, @@ -182,6 +415,16 @@ export function assertReadRouteSessionSourceSafe(session: ReadRouteSession) { session.pipelineOwnership.acceptedNeedRequiredBeforeFindingFits === true && session.pipelineOwnership.previewSourceSafeBeforeSettlement === true && session.pipelineOwnership.deliveryRequiresPaidReadRights === true && + session.procurementGovernance.schema === 'bitcode.read.procurement-governance' && + session.procurementGovernance.quotePolicy.pricingVersion === 'measurement-weight-volume' && + session.procurementGovernance.quotePolicy.shareToFee.deterministic === true && + (session.procurementGovernance.budgetPolicy.budgetEnvelopeSats >= + session.procurementGovernance.budgetPolicy.quoteSats || + session.procurementGovernance.settlement.readiness === 'blocked-budget') && + session.procurementGovernance.prePurchaseReview.protectedSourceVisible === false && + session.procurementGovernance.prePurchaseReview.unpaidAssetPackSourceVisible === false && + session.procurementGovernance.prePurchaseReview.walletPrivateMaterialVisible === false && + session.procurementGovernance.prePurchaseReview.settlementPrivatePayloadVisible === false && session.disclosure.sourceSafetyClass === 'source_safe_read_route_metadata' && session.disclosure.protectedSourceVisible === false && session.disclosure.unpaidAssetPackSourceVisible === false && diff --git a/uapi/tests/readRouteModel.test.ts b/uapi/tests/readRouteModel.test.ts index 56e823b6..d5486137 100644 --- a/uapi/tests/readRouteModel.test.ts +++ b/uapi/tests/readRouteModel.test.ts @@ -1,5 +1,6 @@ import { assertReadRouteSessionSourceSafe, + buildReadProcurementGovernance, buildReadRouteSession, readReadRouteStage, writeReadRouteStage, @@ -34,6 +35,9 @@ describe('read-route-model', () => { expect(session.pipelineOwnership.acceptedNeedRequiredBeforeFindingFits).toBe(true); expect(session.pipelineOwnership.previewSourceSafeBeforeSettlement).toBe(true); expect(session.pipelineOwnership.deliveryRequiresPaidReadRights).toBe(true); + expect(session.procurementGovernance.schema).toBe('bitcode.read.procurement-governance'); + expect(session.procurementGovernance.quotePolicy.pricingVersion).toBe('measurement-weight-volume'); + expect(session.procurementGovernance.quotePolicy.shareToFee.deterministic).toBe(true); expect(session.disclosure.protectedSourceVisible).toBe(false); expect(session.disclosure.unpaidAssetPackSourceVisible).toBe(false); expect(session.disclosure.rawPromptVisible).toBe(false); @@ -46,6 +50,77 @@ describe('read-route-model', () => { }); }); + it('projects approved Reading quote readiness without exposing source-bearing payloads', () => { + const session = buildReadRouteSession({ + transactionId: 'read-run-quote-approved', + repositoryFullName: 'engineeredsoftware/ENGI', + hasRepositorySource: true, + hasReadMeasurement: true, + hasSynthesizedNeed: true, + hasAcceptedNeed: true, + hasSourceSafePreview: true, + measuredBtd: 120, + budgetEnvelopeSats: 50_000, + approvalThresholdSats: 10_000, + quoteSats: 12_500, + procurementApproved: true, + buyerAuthorized: true, + walletAuthorityPresent: true, + }); + + expect(session.procurementGovernance.budgetPolicy.state).toBe('within-budget'); + expect(session.procurementGovernance.budgetPolicy.approvalRequired).toBe(true); + expect(session.procurementGovernance.quotePolicy.state).toBe('approved'); + expect(session.procurementGovernance.settlement.readiness).toBe('ready-for-testnet-settlement'); + expect(session.procurementGovernance.settlement.btcBtdSettlementReady).toBe(true); + expect(session.procurementGovernance.prePurchaseReview.protectedSourceVisible).toBe(false); + expect(session.procurementGovernance.prePurchaseReview.unpaidAssetPackSourceVisible).toBe(false); + expect(assertReadRouteSessionSourceSafe(session).admitted).toBe(true); + }); + + it('blocks settlement readiness when approval or budget policy is not satisfied', () => { + const approvalRequired = buildReadProcurementGovernance({ + hasSourceSafePreview: true, + measuredBtd: 120, + budgetEnvelopeSats: 50_000, + approvalThresholdSats: 10_000, + quoteSats: 12_500, + buyerAuthorized: true, + walletAuthorityPresent: true, + }); + const budgetExceeded = buildReadProcurementGovernance({ + hasSourceSafePreview: true, + measuredBtd: 120, + budgetEnvelopeSats: 5_000, + approvalThresholdSats: 4_000, + quoteSats: 12_500, + procurementApproved: true, + buyerAuthorized: true, + walletAuthorityPresent: true, + }); + const expiredQuote = buildReadProcurementGovernance({ + hasSourceSafePreview: true, + measuredBtd: 120, + budgetEnvelopeSats: 50_000, + approvalThresholdSats: 40_000, + quoteSats: 12_500, + quoteIssuedAt: '2026-05-29T10:00:00.000Z', + quoteExpiresAt: '2026-05-29T10:30:00.000Z', + quoteObservedAt: '2026-05-29T10:31:00.000Z', + buyerAuthorized: true, + walletAuthorityPresent: true, + }); + + expect(approvalRequired.budgetPolicy.state).toBe('approval-required'); + expect(approvalRequired.settlement.readiness).toBe('awaiting-approval'); + expect(approvalRequired.settlement.blockers).toContain('procurement approval required'); + expect(budgetExceeded.budgetPolicy.state).toBe('exceeded'); + expect(budgetExceeded.quotePolicy.state).toBe('blocked'); + expect(budgetExceeded.settlement.readiness).toBe('blocked-budget'); + expect(expiredQuote.quotePolicy.state).toBe('expired'); + expect(expiredQuote.settlement.readiness).toBe('blocked-expired-quote'); + }); + it('keeps Finding Fits blocked until a synthesized Need is accepted', () => { const session = buildReadRouteSession({ routeReadingStage: 'request-fit',