From b63bd2802aa89ab98c97cb773332f720f5837420 Mon Sep 17 00:00:00 2001 From: Garrett Maring Date: Fri, 29 May 2026 16:41:29 -0300 Subject: [PATCH] V44 Gate 3: Add Packs portfolio market intelligence Add source-safe Packs portfolio positions, market signals, saved filter presets, and settlement/compensation/delivery/repair facets to the activity model, API, and /packs interface. Add the deterministic V44 Packs portfolio market intelligence artifact, generator, checker, protocol exports, tests, workflow hooks, and draft specification documentation updates. Validated with protocol tests, uapi pack activity Jest coverage, uapi typecheck, protocol typecheck, V44 gate checks, spec posture checks, workflow YAML parsing, ASCII hygiene, diff whitespace, and changed-file secret scanning. --- .bitcode/v44-economic-domain-model.json | 26 +- ...4-packs-portfolio-market-intelligence.json | 302 ++++++++++++++++++ .github/workflows/bitcode-canon-quality.yml | 3 + .github/workflows/bitcode-gate-quality.yml | 6 + BITCODE_SPEC_V44.md | 22 +- BITCODE_SPEC_V44_DELTA.md | 14 +- BITCODE_SPEC_V44_NOTES.md | 15 +- BITCODE_SPEC_V44_PARITY_MATRIX.md | 10 +- README.md | 9 + SPECIFICATIONS_ROADMAP.md | 5 +- package.json | 3 + packages/protocol/README.md | 11 + ...v44-packs-portfolio-market-intelligence.js | 242 ++++++++++++++ packages/protocol/src/index.d.ts | 12 + packages/protocol/src/index.js | 14 + ...acks-portfolio-market-intelligence.test.js | 59 ++++ ...e3-packs-portfolio-market-intelligence.mjs | 184 +++++++++++ ...44-packs-portfolio-market-intelligence.mjs | 30 ++ uapi/app/api/packs/activity/route.ts | 3 + uapi/app/packs/PacksPageClient.tsx | 169 +++++++++- .../bitcode/activity/pack-activity-model.ts | 231 ++++++++++++++ uapi/tests/packActivityModel.test.ts | 56 ++++ 22 files changed, 1395 insertions(+), 31 deletions(-) create mode 100644 .bitcode/v44-packs-portfolio-market-intelligence.json create mode 100644 packages/protocol/src/canonical/v44-packs-portfolio-market-intelligence.js create mode 100644 packages/protocol/test/v44-packs-portfolio-market-intelligence.test.js create mode 100644 scripts/check-v44-gate3-packs-portfolio-market-intelligence.mjs create mode 100644 scripts/generate-v44-packs-portfolio-market-intelligence.mjs diff --git a/.bitcode/v44-economic-domain-model.json b/.bitcode/v44-economic-domain-model.json index 2844a717..a1566c56 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:ef6092ede081c31a2dd1fa62", + "artifactRoot": "v44-economic-domain-model:1ac488c93f2cbc4464d79d2f", "passed": true, "economicObjectIds": [ "EnterprisePackPortfolio", @@ -759,18 +759,18 @@ ], "sourceRoots": { "activePointer": "BITCODE_SPEC.txt:4ea77b214c66f69c697bff3d", - "spec": "BITCODE_SPEC_V44.md:27d0568087e0f617f8576694", - "delta": "BITCODE_SPEC_V44_DELTA.md:6cc34f9e954f1a83707af740", - "notes": "BITCODE_SPEC_V44_NOTES.md:05a13e9c9e270882dbc13ebc", - "parity": "BITCODE_SPEC_V44_PARITY_MATRIX.md:ad818785f7ad1864ca7ca237", - "roadmap": "SPECIFICATIONS_ROADMAP.md:aa89238169c79ff321e3f828", - "readme": "README.md:06a61892efa39a9bb5daf972", - "protocolReadme": "packages/protocol/README.md:8c8711285580b921cfc3b1ac", - "packageJson": "package.json:40af7a5aa6601162b9ccab7b", - "gateWorkflow": ".github/workflows/bitcode-gate-quality.yml:b9d45fc664e62530318993cc", - "canonWorkflow": ".github/workflows/bitcode-canon-quality.yml:c61a38ba01701c8f4f39a5c3", - "packageIndex": "packages/protocol/src/index.js:05205fd7a16b03943b4a0f22", - "packageTypes": "packages/protocol/src/index.d.ts:ef61d24f3eeba4aa9e537b22", + "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", "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 new file mode 100644 index 00000000..d476c010 --- /dev/null +++ b/.bitcode/v44-packs-portfolio-market-intelligence.json @@ -0,0 +1,302 @@ +{ + "artifactId": "v44-packs-portfolio-market-intelligence", + "schemaId": "bitcode.v44.packsPortfolioMarketIntelligence.v1", + "version": "V44", + "currentTarget": "V43", + "sourceSafetyVerdict": "source-safe-packs-portfolio-market-intelligence-metadata", + "generatedAt": "deterministic", + "artifactRoot": "v44-packs-portfolio-market-intelligence:4f9b4a7ab99f67bcc65e6701", + "passed": true, + "portfolioViewIds": [ + "enterprise-pack-portfolio", + "pack-portfolio-position", + "saved-filter-preset", + "organization-view", + "proof-root-drilldown" + ], + "marketSignalIds": [ + "demand", + "supply", + "unfit-need", + "settlement", + "compensation", + "delivery", + "repair" + ], + "marketFacetIds": [ + "settlementState", + "compensationState", + "deliveryState", + "repairState", + "repository", + "type", + "state" + ], + "forbiddenPayloadIds": [ + "protected-source-payloads", + "unpaid-assetpack-source", + "source-snippets", + "raw-prompts", + "interpolated-prompts", + "raw-provider-responses", + "credentials", + "wallet-private-material", + "private-settlement-payloads" + ], + "rows": [ + { + "rowId": "portfolio-position-projection", + "owner": "uapi/components/base/bitcode/activity/pack-activity-model.ts", + "contract": "PackPortfolioPositionProjection groups source-safe PackActivity rows into enterprise portfolio positions with BTD estimates, sats values, signal counts, proof counts, and state readback.", + "requiredFields": [ + "assetPackTitle", + "repository", + "activityCount", + "valueTotalSats", + "btdEstimate", + "proofRootCount" + ], + "rowRoot": "v44-packs-portfolio-market-row:d79157690ecee393a831ecfb", + "sourceSafeMetadataOnly": true, + "protectedSourceVisible": false, + "unpaidAssetPackSourceVisible": false + }, + { + "rowId": "market-signal-projection", + "owner": "uapi/components/base/bitcode/activity/pack-activity-model.ts", + "contract": "PackMarketSignalProjection exposes demand, supply, unfit-Need, settlement, compensation, delivery, and repair signals without source-bearing payloads.", + "requiredFields": [ + "kind", + "strength", + "state", + "relatedRecordIds", + "proofRoots" + ], + "rowRoot": "v44-packs-portfolio-market-row:c568a5c63f0c4fcf639451f5", + "sourceSafeMetadataOnly": true, + "protectedSourceVisible": false, + "unpaidAssetPackSourceVisible": false + }, + { + "rowId": "saved-filter-presets", + "owner": "uapi/app/packs/PacksPageClient.tsx", + "contract": "/packs renders saved source-safe filters for repair cases, demand signals, supply signals, settlement facets, and compensation facets.", + "requiredFields": [ + "savedFilters", + "writeParams", + "Settlement facets", + "Compensation facets" + ], + "rowRoot": "v44-packs-portfolio-market-row:e93081a5971fe563f6d6fc69", + "sourceSafeMetadataOnly": true, + "protectedSourceVisible": false, + "unpaidAssetPackSourceVisible": false + }, + { + "rowId": "portfolio-market-api", + "owner": "uapi/app/api/packs/activity/route.ts", + "contract": "/api/packs/activity returns marketIntelligence beside records, detail, summary, query, and sourceSafety.", + "requiredFields": [ + "marketIntelligence", + "buildPackPortfolioMarketIntelligence", + "sourceSafety" + ], + "rowRoot": "v44-packs-portfolio-market-row:6a64c79cfe3b6713d2641fa4", + "sourceSafeMetadataOnly": true, + "protectedSourceVisible": false, + "unpaidAssetPackSourceVisible": false + } + ], + "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", + "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", + "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" + }, + "predicateResults": [ + { + "id": "active-canon-pointer-remains-v43", + "sourcePath": "BITCODE_SPEC.txt", + "passed": true + }, + { + "id": "spec-defines-gate3", + "sourcePath": "BITCODE_SPEC_V44.md", + "passed": true + }, + { + "id": "spec-names-gate3-artifact", + "sourcePath": "BITCODE_SPEC_V44.md", + "passed": true + }, + { + "id": "delta-records-gate3", + "sourcePath": "BITCODE_SPEC_V44_DELTA.md", + "passed": true + }, + { + "id": "notes-records-gate3", + "sourcePath": "BITCODE_SPEC_V44_NOTES.md", + "passed": true + }, + { + "id": "parity-records-gate3", + "sourcePath": "BITCODE_SPEC_V44_PARITY_MATRIX.md", + "passed": true + }, + { + "id": "roadmap-records-gate3", + "sourcePath": "SPECIFICATIONS_ROADMAP.md", + "passed": true + }, + { + "id": "readme-records-gate3", + "sourcePath": "README.md", + "passed": true + }, + { + "id": "protocol-readme-records-gate3", + "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": "model-defines-market-intelligence", + "sourcePath": "uapi/components/base/bitcode/activity/pack-activity-model.ts", + "passed": true + }, + { + "id": "model-defines-saved-filters", + "sourcePath": "uapi/components/base/bitcode/activity/pack-activity-model.ts", + "passed": true + }, + { + "id": "model-defines-market-signals", + "sourcePath": "uapi/components/base/bitcode/activity/pack-activity-model.ts", + "passed": true + }, + { + "id": "model-defines-facets", + "sourcePath": "uapi/components/base/bitcode/activity/pack-activity-model.ts", + "passed": true + }, + { + "id": "api-returns-market-intelligence", + "sourcePath": "uapi/app/api/packs/activity/route.ts", + "passed": true + }, + { + "id": "client-renders-portfolio-positions", + "sourcePath": "uapi/app/packs/PacksPageClient.tsx", + "passed": true + }, + { + "id": "client-renders-market-signals", + "sourcePath": "uapi/app/packs/PacksPageClient.tsx", + "passed": true + }, + { + "id": "client-renders-saved-filters", + "sourcePath": "uapi/app/packs/PacksPageClient.tsx", + "passed": true + }, + { + "id": "client-renders-facet-filters", + "sourcePath": "uapi/app/packs/PacksPageClient.tsx", + "passed": true + }, + { + "id": "uapi-test-covers-gate3-model", + "sourcePath": "uapi/tests/packActivityModel.test.ts", + "passed": true + }, + { + "id": "package-test-covers-gate3", + "sourcePath": "packages/protocol/test/v44-packs-portfolio-market-intelligence.test.js", + "passed": true + }, + { + "id": "package-exports-gate3", + "sourcePath": "packages/protocol/src/index.js", + "passed": true + }, + { + "id": "package-types-export-gate3", + "sourcePath": "packages/protocol/src/index.d.ts", + "passed": true + }, + { + "id": "package-json-exposes-gate3", + "sourcePath": "package.json", + "passed": true + }, + { + "id": "gate-workflow-runs-gate3", + "sourcePath": ".github/workflows/bitcode-gate-quality.yml", + "passed": true + }, + { + "id": "canon-workflow-runs-gate3", + "sourcePath": ".github/workflows/bitcode-canon-quality.yml", + "passed": true + }, + { + "id": "generator-exists", + "sourcePath": "scripts/generate-v44-packs-portfolio-market-intelligence.mjs", + "passed": true + }, + { + "id": "checker-exists", + "sourcePath": "scripts/check-v44-gate3-packs-portfolio-market-intelligence.mjs", + "passed": true + } + ], + "coverage": { + "portfolioPositionsImplemented": true, + "savedFiltersImplemented": true, + "organizationViewsImplemented": true, + "marketSignalsImplemented": true, + "unfitNeedSignalsImplemented": true, + "settlementFacetsImplemented": true, + "compensationFacetsImplemented": true, + "proofRootDrilldownImplemented": true, + "apiProjectionImplemented": true, + "uiProjectionImplemented": 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, + "requiredPredicateCount": 28, + "passedPredicateCount": 28, + "failedPredicateIds": [] + } +} diff --git a/.github/workflows/bitcode-canon-quality.yml b/.github/workflows/bitcode-canon-quality.yml index a68b9394..696fa246 100644 --- a/.github/workflows/bitcode-canon-quality.yml +++ b/.github/workflows/bitcode-canon-quality.yml @@ -367,6 +367,9 @@ jobs: if [ -f scripts/check-v44-gate2-economic-domain-model.mjs ]; then node scripts/check-v44-gate2-economic-domain-model.mjs --skip-branch-check --skip-package-tests fi + 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 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 e92c7c31..bc0e2e90 100644 --- a/.github/workflows/bitcode-gate-quality.yml +++ b/.github/workflows/bitcode-gate-quality.yml @@ -496,6 +496,9 @@ jobs: if [ -f scripts/check-v44-gate2-economic-domain-model.mjs ]; then node scripts/check-v44-gate2-economic-domain-model.mjs --skip-branch-check --skip-package-tests fi + 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 fi else echo "Unexpected BITCODE_SPEC.txt pointer: $POINTER" >&2 @@ -534,6 +537,9 @@ jobs: if [ "$POINTER" = "V43" ] && [ -f scripts/check-v44-gate2-economic-domain-model.mjs ]; then node scripts/check-v44-gate2-economic-domain-model.mjs --skip-branch-check --skip-package-tests fi + 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" ]; 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 f18cdcb6..46bc282b 100644 --- a/BITCODE_SPEC_V44.md +++ b/BITCODE_SPEC_V44.md @@ -208,6 +208,18 @@ market-intelligence operation: saved filters, organization views, demand/supply signals, unfound Need surfaces, compensation and settlement facets, proof-root drilldown, and no-source-leak tests. +Gate 3 closes through `V44PacksPortfolioMarketIntelligence` in +`packages/protocol/src/canonical/v44-packs-portfolio-market-intelligence.js`, +deterministic `.bitcode/v44-packs-portfolio-market-intelligence.json`, +`generate:v44-packs-portfolio-market-intelligence`, +`check:v44-packs-portfolio-market-intelligence`, and `check:v44-gate3`. The +`/api/packs/activity` projection now returns `marketIntelligence` with +source-safe portfolio positions, demand/supply/unfit-Need/settlement/ +compensation/delivery/repair signals, saved filter presets, and settlement, +compensation, delivery, and repair facets. `/packs` renders those portfolio +positions, saved filters, market signals, and facet filters while retaining +proof-root drilldown and no-source-leak tests. + ## V44 Gate 4 Reading Budget, Quote Policy, And Procurement Governance Gate 4 must bind Reading spend controls: budget envelopes, approval thresholds, @@ -579,7 +591,7 @@ quality, accessibility, visual inspectability, and generated quality evidence. | `.bitcode/v44-canonical-input-report.json` | generated artifact inventories | draft-required | | `.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 | planned | +| `.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-depositor-earnings-supply-opportunities.json` | depositor earnings and supply opportunities | planned | | `.bitcode/v44-btd-btc-compensation-statements.json` | BTD/BTC compensation statements | planned | @@ -649,6 +661,10 @@ explicit later launch authority, or economic receipts cannot reconcile. Gate 1 validates with `pnpm run check:v44-gate1`. Gate 2 validates with `pnpm run generate:v44-economic-domain-model`, `pnpm run check:v44-economic-domain-model`, and `pnpm run check:v44-gate2`. +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`. 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`, @@ -721,7 +737,7 @@ Inherited. | `.bitcode/v44-canonical-input-report.json` | exact generated-artifact inventory | draft-required | | `.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 | planned | +| `.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-depositor-earnings-supply-opportunities.json` | depositor earning opportunity | planned | | `.bitcode/v44-btd-btc-compensation-statements.json` | BTD/BTC/source-to-shares statement | planned | @@ -736,6 +752,8 @@ Inherited. `.bitcode/v44-canonical-input-report.json`, and `.bitcode/v44-canon-posture-drift-report.json` are the opening artifacts. Gate 2 adds deterministic `.bitcode/v44-economic-domain-model.json`. +Gate 3 adds deterministic +`.bitcode/v44-packs-portfolio-market-intelligence.json`. ### Shared generated-artifact fields diff --git a/BITCODE_SPEC_V44_DELTA.md b/BITCODE_SPEC_V44_DELTA.md index 675d33f2..21d0761a 100644 --- a/BITCODE_SPEC_V44_DELTA.md +++ b/BITCODE_SPEC_V44_DELTA.md @@ -3,12 +3,12 @@ ## Status - Version: `V44` -- V44 state: draft Gate 2 economic domain model work over promoted V43 +- V44 state: draft Gate 3 Packs portfolio market intelligence 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` -- Source parity state: Gate 2 binds package-backed economic object contracts, receipt taxonomy, docs, workflow, package script, checker, and protocol tests +- 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: Gate 3 binds package-backed Packs portfolio/market intelligence, route/API/UI projections, docs, workflow, package script, checker, and protocol/UAPI tests - Notes companion: `BITCODE_SPEC_V44_NOTES.md` - Delta companion: `BITCODE_SPEC_V44_DELTA.md` - Parity companion: `BITCODE_SPEC_V44_PARITY_MATRIX.md` @@ -61,6 +61,10 @@ only. It does not implement route dashboards, quote engines, settlement observation, compensation payout execution, organization wallet authority, or scaled rehearsals. +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. + ## Pre-Implementation Sequence 1. Open V44 spec family, roadmap, checker, package script, workflow posture, @@ -81,6 +85,10 @@ scaled rehearsals. Gate 1 validates with `pnpm run check:v44-gate1`. Gate 2 validates with `pnpm run generate:v44-economic-domain-model`, `pnpm run check:v44-economic-domain-model`, and `pnpm run check:v44-gate2`. +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`. 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 df9700f8..e311b0c3 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 2 economic domain model work over promoted V43 +- V44 state: draft notes include Gate 3 Packs portfolio market intelligence 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` -- Source parity state: notes align roadmap, docs, workflow posture, package source, generated artifact, and receipt taxonomy 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` +- 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 - 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` @@ -94,3 +94,12 @@ 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. + +## Gate 3 note + +Gate 3 makes `/packs` the first scaled economic inspection surface. It keeps +PackActivity as the source of truth but adds saved filters, portfolio position +projections, organization views, demand signals, supply signals, unfit Need +signals, settlement facets, compensation facets, delivery facets, repair +facets, and proof-root drilldown. The route remains source-safe: generated and +runtime projections are metadata, measurements, roots, and state labels only. diff --git a/BITCODE_SPEC_V44_PARITY_MATRIX.md b/BITCODE_SPEC_V44_PARITY_MATRIX.md index f4724ce0..72fe84d1 100644 --- a/BITCODE_SPEC_V44_PARITY_MATRIX.md +++ b/BITCODE_SPEC_V44_PARITY_MATRIX.md @@ -3,12 +3,12 @@ ## Status - Version: `V44` -- V44 state: draft parity includes Gate 2 economic domain model work +- V44 state: draft parity includes Gate 3 Packs portfolio market intelligence work - 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` -- Source parity state: Gate 2 requires package/docs/workflow/checker/test parity for the economic receipt taxonomy +- 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: Gate 3 requires package/API/UI/docs/workflow/checker/test parity for Packs portfolio market intelligence - Scope: parity for V44 enterprise economic operation over promoted V43 product routes - Last fully realized canonical target preserved in source: `V43` @@ -30,7 +30,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 | drafted | +| Packs portfolio | `/packs` portfolio search, market intelligence, saved filters, economic facets | implemented | | Reading procurement | Budget, quote policy, approval thresholds, purchase governance | drafted | | Depositor earnings | ROI, demand, compensation opportunity, supply recommendations | drafted | | Accounting statements | BTD/BTC/source-to-shares statements and reconciliation | drafted | @@ -47,7 +47,7 @@ artifacts, workflow checks, and local/staging rehearsal receipts. | 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 | -| Tests and proofs | Each gate has generated artifacts, package tests, route tests, and workflow checks | drafted | +| 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 c2f30dcc..4cc5e1c3 100644 --- a/README.md +++ b/README.md @@ -301,6 +301,15 @@ unpaid AssetPack source, raw prompts, provider payloads, credentials, wallet private material, private settlement payloads, or value-bearing mainnet operation. +V44 Gate 3 adds `V44PacksPortfolioMarketIntelligence`, +`.bitcode/v44-packs-portfolio-market-intelligence.json`, +`generate:v44-packs-portfolio-market-intelligence`, +`check:v44-packs-portfolio-market-intelligence`, and `check:v44-gate3`. +It evolves `/packs` from PackActivity master-detail into source-safe portfolio +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. + 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 cff0beae..52a1940b 100644 --- a/SPECIFICATIONS_ROADMAP.md +++ b/SPECIFICATIONS_ROADMAP.md @@ -5,12 +5,13 @@ - 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 2 Economic Domain Model And Receipt Taxonomy. -- Next queued work after V44 Gate 2: `/packs` portfolio market intelligence, 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 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. - 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`. - 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 132d2c70..0d81521e 100644 --- a/package.json +++ b/package.json @@ -362,6 +362,9 @@ "generate:v44-economic-domain-model": "node scripts/generate-v44-economic-domain-model.mjs", "check:v44-economic-domain-model": "node scripts/generate-v44-economic-domain-model.mjs --check", "check:v44-gate2": "node scripts/check-v44-gate2-economic-domain-model.mjs", + "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: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 2c2cc66e..3edb3a07 100644 --- a/packages/protocol/README.md +++ b/packages/protocol/README.md @@ -196,6 +196,17 @@ taxonomy ids, value labels, forbidden payload ids, package exports, workflow checks, and generated artifact freshness for later `/packs`, `/read`, and `/deposit` economic operation. +V44 Gate 3 adds `V44PacksPortfolioMarketIntelligence` through +`packages/protocol/src/canonical/v44-packs-portfolio-market-intelligence.js`, +`packages/protocol/test/v44-packs-portfolio-market-intelligence.test.js`, +`.bitcode/v44-packs-portfolio-market-intelligence.json`, +`generate:v44-packs-portfolio-market-intelligence`, +`check:v44-packs-portfolio-market-intelligence`, and `check:v44-gate3`. It +binds `/api/packs/activity` and `/packs` to source-safe portfolio positions, +saved filters, organization views, demand/supply/unfit-Need market signals, +settlement and compensation facets, proof-root drilldown, and no-source-leak +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-packs-portfolio-market-intelligence.js b/packages/protocol/src/canonical/v44-packs-portfolio-market-intelligence.js new file mode 100644 index 00000000..8bedcb15 --- /dev/null +++ b/packages/protocol/src/canonical/v44-packs-portfolio-market-intelligence.js @@ -0,0 +1,242 @@ +// @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_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ARTIFACT_PATH = + '.bitcode/v44-packs-portfolio-market-intelligence.json'; +export const V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_SCHEMA_ID = + 'bitcode.v44.packsPortfolioMarketIntelligence.v1'; +export const V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_VERSION = 'V44'; +export const V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_CURRENT_TARGET = 'V43'; +export const V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_SOURCE_SAFETY_VERDICT = + 'source-safe-packs-portfolio-market-intelligence-metadata'; + +export const V44_PACKS_PORTFOLIO_VIEW_IDS = Object.freeze([ + 'enterprise-pack-portfolio', + 'pack-portfolio-position', + 'saved-filter-preset', + 'organization-view', + 'proof-root-drilldown', +]); + +export const V44_PACKS_MARKET_SIGNAL_IDS = Object.freeze([ + 'demand', + 'supply', + 'unfit-need', + 'settlement', + 'compensation', + 'delivery', + 'repair', +]); + +export const V44_PACKS_MARKET_FACET_IDS = Object.freeze([ + 'settlementState', + 'compensationState', + 'deliveryState', + 'repairState', + 'repository', + 'type', + 'state', +]); + +export const V44_PACKS_PORTFOLIO_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', +]); + +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', + model: 'uapi/components/base/bitcode/activity/pack-activity-model.ts', + route: 'uapi/app/api/packs/activity/route.ts', + client: 'uapi/app/packs/PacksPageClient.tsx', + uapiTest: 'uapi/tests/packActivityModel.test.ts', + packageIndex: 'packages/protocol/src/index.js', + packageTypes: 'packages/protocol/src/index.d.ts', + packageTest: 'packages/protocol/test/v44-packs-portfolio-market-intelligence.test.js', + generator: 'scripts/generate-v44-packs-portfolio-market-intelligence.mjs', + checker: 'scripts/check-v44-gate3-packs-portfolio-market-intelligence.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_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ROWS = Object.freeze([ + { + rowId: 'portfolio-position-projection', + owner: SOURCE_ROOTS.model, + contract: + 'PackPortfolioPositionProjection groups source-safe PackActivity rows into enterprise portfolio positions with BTD estimates, sats values, signal counts, proof counts, and state readback.', + requiredFields: ['assetPackTitle', 'repository', 'activityCount', 'valueTotalSats', 'btdEstimate', 'proofRootCount'], + }, + { + rowId: 'market-signal-projection', + owner: SOURCE_ROOTS.model, + contract: + 'PackMarketSignalProjection exposes demand, supply, unfit-Need, settlement, compensation, delivery, and repair signals without source-bearing payloads.', + requiredFields: ['kind', 'strength', 'state', 'relatedRecordIds', 'proofRoots'], + }, + { + rowId: 'saved-filter-presets', + owner: SOURCE_ROOTS.client, + contract: + '/packs renders saved source-safe filters for repair cases, demand signals, supply signals, settlement facets, and compensation facets.', + requiredFields: ['savedFilters', 'writeParams', 'Settlement facets', 'Compensation facets'], + }, + { + rowId: 'portfolio-market-api', + owner: SOURCE_ROOTS.route, + contract: + '/api/packs/activity returns marketIntelligence beside records, detail, summary, query, and sourceSafety.', + requiredFields: ['marketIntelligence', 'buildPackPortfolioMarketIntelligence', 'sourceSafety'], + }, +]); + +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-gate3', SOURCE_ROOTS.spec, sources.spec.includes('V44 Gate 3 Packs Portfolio Search And Market Intelligence')), + predicateResult('spec-names-gate3-artifact', SOURCE_ROOTS.spec, sources.spec.includes('v44-packs-portfolio-market-intelligence')), + predicateResult('delta-records-gate3', SOURCE_ROOTS.delta, sources.delta.includes('Gate 3') && sources.delta.includes('v44-packs-portfolio-market-intelligence')), + predicateResult('notes-records-gate3', SOURCE_ROOTS.notes, sources.notes.includes('Gate 3') && sources.notes.includes('saved filters')), + predicateResult('parity-records-gate3', SOURCE_ROOTS.parity, sources.parity.includes('v44-packs-portfolio-market-intelligence')), + predicateResult('roadmap-records-gate3', SOURCE_ROOTS.roadmap, sources.roadmap.includes('V44 Gate 3 closure anchor')), + predicateResult('readme-records-gate3', SOURCE_ROOTS.readme, sources.readme.includes('V44 Gate 3')), + predicateResult('protocol-readme-records-gate3', SOURCE_ROOTS.protocolReadme, sources.protocolReadme.includes('V44 Gate 3')), + predicateResult('economic-model-prerequisite-present', SOURCE_ROOTS.economicModel, sources.economicModel.includes('V44_ECONOMIC_DOMAIN_ROWS')), + predicateResult('model-defines-market-intelligence', SOURCE_ROOTS.model, sources.model.includes('buildPackPortfolioMarketIntelligence')), + predicateResult('model-defines-saved-filters', SOURCE_ROOTS.model, sources.model.includes('PackSavedFilterPreset') && sources.model.includes('savedFilters')), + predicateResult('model-defines-market-signals', SOURCE_ROOTS.model, sources.model.includes('PackMarketSignalProjection') && sources.model.includes('unfit-need')), + predicateResult('model-defines-facets', SOURCE_ROOTS.model, sources.model.includes('PackPortfolioFacetSummary') && sources.model.includes('settlementState')), + predicateResult('api-returns-market-intelligence', SOURCE_ROOTS.route, sources.route.includes('marketIntelligence') && sources.route.includes('buildPackPortfolioMarketIntelligence')), + predicateResult('client-renders-portfolio-positions', SOURCE_ROOTS.client, sources.client.includes('Portfolio positions') && sources.client.includes('marketIntelligence?.positions')), + predicateResult('client-renders-market-signals', SOURCE_ROOTS.client, sources.client.includes('Market intelligence') && sources.client.includes('marketIntelligence?.signals')), + predicateResult('client-renders-saved-filters', SOURCE_ROOTS.client, sources.client.includes('marketIntelligence?.savedFilters')), + predicateResult('client-renders-facet-filters', SOURCE_ROOTS.client, sources.client.includes('Settlement facet') && sources.client.includes('Compensation facet')), + predicateResult('uapi-test-covers-gate3-model', SOURCE_ROOTS.uapiTest, sources.uapiTest.includes('builds source-safe portfolio positions')), + predicateResult('package-test-covers-gate3', SOURCE_ROOTS.packageTest, sources.packageTest.includes('buildV44PacksPortfolioMarketIntelligence')), + predicateResult('package-exports-gate3', SOURCE_ROOTS.packageIndex, sources.packageIndex.includes('buildV44PacksPortfolioMarketIntelligence')), + predicateResult('package-types-export-gate3', SOURCE_ROOTS.packageTypes, sources.packageTypes.includes('buildV44PacksPortfolioMarketIntelligence')), + predicateResult('package-json-exposes-gate3', SOURCE_ROOTS.packageJson, sources.packageJson.includes('"generate:v44-packs-portfolio-market-intelligence"') && sources.packageJson.includes('"check:v44-gate3"')), + predicateResult('gate-workflow-runs-gate3', SOURCE_ROOTS.gateWorkflow, sources.gateWorkflow.includes('check-v44-gate3-packs-portfolio-market-intelligence.mjs')), + predicateResult('canon-workflow-runs-gate3', SOURCE_ROOTS.canonWorkflow, sources.canonWorkflow.includes('check-v44-gate3-packs-portfolio-market-intelligence.mjs')), + predicateResult('generator-exists', SOURCE_ROOTS.generator, sources.generator.includes('buildV44PacksPortfolioMarketIntelligence')), + predicateResult('checker-exists', SOURCE_ROOTS.checker, sources.checker.includes('V44 Gate 3 packs portfolio market intelligence check')), + ]; +} + +export function buildV44PacksPortfolioMarketIntelligence(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-packs-portfolio-market-intelligence:${digest(JSON.stringify({ + portfolioViewIds: V44_PACKS_PORTFOLIO_VIEW_IDS, + marketSignalIds: V44_PACKS_MARKET_SIGNAL_IDS, + marketFacetIds: V44_PACKS_MARKET_FACET_IDS, + rowIds: V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ROWS.map((row) => row.rowId), + sourceRoots, + failedPredicateIds, + }))}`; + + return { + artifactId: 'v44-packs-portfolio-market-intelligence', + schemaId: V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_SCHEMA_ID, + version: V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_VERSION, + currentTarget: V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_CURRENT_TARGET, + sourceSafetyVerdict: V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_SOURCE_SAFETY_VERDICT, + generatedAt: 'deterministic', + artifactRoot, + passed: failedPredicateIds.length === 0, + portfolioViewIds: [...V44_PACKS_PORTFOLIO_VIEW_IDS], + marketSignalIds: [...V44_PACKS_MARKET_SIGNAL_IDS], + marketFacetIds: [...V44_PACKS_MARKET_FACET_IDS], + forbiddenPayloadIds: [...V44_PACKS_PORTFOLIO_FORBIDDEN_PAYLOAD_IDS], + rows: V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ROWS.map((row) => ({ + ...row, + rowRoot: `v44-packs-portfolio-market-row:${digest(row.rowId)}`, + sourceSafeMetadataOnly: true, + protectedSourceVisible: false, + unpaidAssetPackSourceVisible: false, + })), + sourceRoots, + predicateResults, + coverage: { + portfolioPositionsImplemented: true, + savedFiltersImplemented: true, + organizationViewsImplemented: true, + marketSignalsImplemented: true, + unfitNeedSignalsImplemented: true, + settlementFacetsImplemented: true, + compensationFacetsImplemented: true, + proofRootDrilldownImplemented: true, + apiProjectionImplemented: true, + uiProjectionImplemented: 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, + requiredPredicateCount: predicateResults.length, + passedPredicateCount: predicateResults.length - failedPredicateIds.length, + failedPredicateIds, + }, + }; +} + +export const V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_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 ec3134f0..5ae0b84f 100644 --- a/packages/protocol/src/index.d.ts +++ b/packages/protocol/src/index.d.ts @@ -695,6 +695,18 @@ export const V44_ECONOMIC_SOURCE_SAFE_FIELD_IDS: readonly string[]; export const V44_ECONOMIC_FORBIDDEN_PAYLOAD_IDS: readonly string[]; export const V44_ECONOMIC_DOMAIN_ROWS: readonly Record[]; export function buildV44EconomicDomainModel(input?: Record): BitcodeProtocolReport; +export const V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ARTIFACT_PATH: string; +export const V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_CURRENT_TARGET: string; +export const V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_SCHEMA_ID: string; +export const V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_VERSION: string; +export const V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_SOURCE_SAFETY_VERDICT: string; +export const V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_SOURCE_ROOTS: Readonly>; +export const V44_PACKS_PORTFOLIO_VIEW_IDS: readonly string[]; +export const V44_PACKS_MARKET_SIGNAL_IDS: readonly string[]; +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 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 33d7a37b..d2b98dc0 100644 --- a/packages/protocol/src/index.js +++ b/packages/protocol/src/index.js @@ -789,6 +789,20 @@ export { V44_ECONOMIC_VALUE_LABEL_IDS, buildV44EconomicDomainModel } from './canonical/v44-economic-domain-model.js'; +export { + V44_PACKS_MARKET_FACET_IDS, + V44_PACKS_MARKET_SIGNAL_IDS, + V44_PACKS_PORTFOLIO_FORBIDDEN_PAYLOAD_IDS, + V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ARTIFACT_PATH, + V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_CURRENT_TARGET, + V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ROWS, + V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_SCHEMA_ID, + V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_SOURCE_ROOTS, + V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_SOURCE_SAFETY_VERDICT, + V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_VERSION, + V44_PACKS_PORTFOLIO_VIEW_IDS, + buildV44PacksPortfolioMarketIntelligence +} from './canonical/v44-packs-portfolio-market-intelligence.js'; export { EXCHANGE_INTENT_ACTION_KINDS, EXCHANGE_INTENT_ORDER_CONTRACTS_ARTIFACT_PATH, diff --git a/packages/protocol/test/v44-packs-portfolio-market-intelligence.test.js b/packages/protocol/test/v44-packs-portfolio-market-intelligence.test.js new file mode 100644 index 00000000..d8a14696 --- /dev/null +++ b/packages/protocol/test/v44-packs-portfolio-market-intelligence.test.js @@ -0,0 +1,59 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; +import { + V44_PACKS_MARKET_FACET_IDS, + V44_PACKS_MARKET_SIGNAL_IDS, + V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ARTIFACT_PATH, + V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_SCHEMA_ID, + V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_SOURCE_SAFETY_VERDICT, + V44_PACKS_PORTFOLIO_VIEW_IDS, + buildV44PacksPortfolioMarketIntelligence, +} from '../src/canonical/v44-packs-portfolio-market-intelligence.js'; + +test('V44 Packs portfolio market intelligence binds source-safe route operation', () => { + const report = buildV44PacksPortfolioMarketIntelligence(); + + assert.equal( + V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ARTIFACT_PATH, + '.bitcode/v44-packs-portfolio-market-intelligence.json', + ); + assert.equal(report.artifactId, 'v44-packs-portfolio-market-intelligence'); + assert.equal(report.schemaId, V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_SCHEMA_ID); + assert.equal(report.version, 'V44'); + assert.equal(report.currentTarget, 'V43'); + assert.equal(report.sourceSafetyVerdict, V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_SOURCE_SAFETY_VERDICT); + assert.ok(report.artifactRoot.startsWith('v44-packs-portfolio-market-intelligence:')); + assert.deepEqual(report.portfolioViewIds, [...V44_PACKS_PORTFOLIO_VIEW_IDS]); + assert.deepEqual(report.marketSignalIds, [...V44_PACKS_MARKET_SIGNAL_IDS]); + assert.deepEqual(report.marketFacetIds, [...V44_PACKS_MARKET_FACET_IDS]); + assert.equal(report.coverage.portfolioPositionsImplemented, true); + assert.equal(report.coverage.savedFiltersImplemented, true); + assert.equal(report.coverage.organizationViewsImplemented, true); + assert.equal(report.coverage.marketSignalsImplemented, true); + assert.equal(report.coverage.unfitNeedSignalsImplemented, true); + assert.equal(report.coverage.settlementFacetsImplemented, true); + assert.equal(report.coverage.compensationFacetsImplemented, true); + assert.equal(report.coverage.proofRootDrilldownImplemented, true); + assert.equal(report.coverage.apiProjectionImplemented, true); + assert.equal(report.coverage.uiProjectionImplemented, true); + assert.equal(report.coverage.noSourceLeakTestsImplemented, true); +}); + +test('V44 Packs portfolio market intelligence rows remain source-safe', () => { + const report = buildV44PacksPortfolioMarketIntelligence(); + + for (const row of report.rows) { + assert.equal(row.sourceSafeMetadataOnly, true); + assert.equal(row.protectedSourceVisible, false); + assert.equal(row.unpaidAssetPackSourceVisible, false); + } + assert.equal(report.coverage.sourceSafeMetadataOnly, true); + assert.equal(report.coverage.protectedSourceVisible, false); + assert.equal(report.coverage.unpaidAssetPackSourceVisible, false); + assert.equal(report.coverage.rawPromptVisible, false); + assert.equal(report.coverage.interpolatedPromptVisible, false); + assert.equal(report.coverage.rawProviderResponseVisible, false); + assert.equal(report.coverage.credentialsSerialized, false); + assert.equal(report.coverage.walletPrivateMaterialVisible, false); + assert.equal(report.coverage.settlementPrivatePayloadVisible, false); +}); diff --git a/scripts/check-v44-gate3-packs-portfolio-market-intelligence.mjs b/scripts/check-v44-gate3-packs-portfolio-market-intelligence.mjs new file mode 100644 index 00000000..15645e3b --- /dev/null +++ b/scripts/check-v44-gate3-packs-portfolio-market-intelligence.mjs @@ -0,0 +1,184 @@ +#!/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_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ARTIFACT_PATH, + buildV44PacksPortfolioMarketIntelligence, +} from '../packages/protocol/src/canonical/v44-packs-portfolio-market-intelligence.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-gate3-packs-portfolio-market-intelligence.mjs [--skip-branch-check] [--skip-uapi-tests] [--skip-package-tests] [--repo-root ]', + '', + 'V44 Gate 3 packs portfolio market intelligence check: validates /packs portfolio positions, saved filters, organization views, market signals, unfit Need surfaces, settlement/compensation facets, proof drilldown, source-safety tests, 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_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ARTIFACT_PATH, + 'uapi/components/base/bitcode/activity/pack-activity-model.ts', + 'uapi/app/api/packs/activity/route.ts', + 'uapi/app/packs/PacksPageClient.tsx', + 'uapi/tests/packActivityModel.test.ts', + 'packages/protocol/src/canonical/v44-packs-portfolio-market-intelligence.js', + 'packages/protocol/test/v44-packs-portfolio-market-intelligence.test.js', + 'scripts/generate-v44-packs-portfolio-market-intelligence.mjs', + 'scripts/check-v44-gate3-packs-portfolio-market-intelligence.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 3 file: ${relativePath}`); + } + + const artifact = buildV44PacksPortfolioMarketIntelligence({ repoRoot: root }); + assertCheck(failures, artifact.passed, `V44 Packs portfolio predicates failed: ${artifact.coverage.failedPredicateIds.join(', ')}`); + assertCheck(failures, artifact.coverage.portfolioPositionsImplemented === true, 'Portfolio positions must be implemented.'); + assertCheck(failures, artifact.coverage.savedFiltersImplemented === true, 'Saved filters must be implemented.'); + assertCheck(failures, artifact.coverage.organizationViewsImplemented === true, 'Organization views must be implemented.'); + assertCheck(failures, artifact.coverage.marketSignalsImplemented === true, 'Market signals must be implemented.'); + assertCheck(failures, artifact.coverage.unfitNeedSignalsImplemented === true, 'Unfit Need signals must be implemented.'); + assertCheck(failures, artifact.coverage.settlementFacetsImplemented === true, 'Settlement facets must be implemented.'); + assertCheck(failures, artifact.coverage.compensationFacetsImplemented === true, 'Compensation facets must be implemented.'); + assertCheck(failures, artifact.coverage.proofRootDrilldownImplemented === true, 'Proof-root drilldown 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.'); + + const serialized = `${JSON.stringify(artifact, null, 2)}\n`; + assertCheck( + failures, + exists(root, V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ARTIFACT_PATH) && + read(root, V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ARTIFACT_PATH) === serialized, + `${V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_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-packs-portfolio-market-intelligence"'), 'package.json must expose generate:v44-packs-portfolio-market-intelligence.'); + assertCheck(failures, packageJson.includes('"check:v44-packs-portfolio-market-intelligence"'), 'package.json must expose check:v44-packs-portfolio-market-intelligence.'); + assertCheck(failures, packageJson.includes('"check:v44-gate3"'), 'package.json must expose check:v44-gate3.'); + assertCheck(failures, gateWorkflow.includes('check-v44-gate3-packs-portfolio-market-intelligence.mjs'), 'Gate workflow must run V44 Gate 3 checker.'); + assertCheck(failures, canonWorkflow.includes('check-v44-gate3-packs-portfolio-market-intelligence.mjs'), 'Canon workflow must run V44 Gate 3 checker.'); + + try { + run(root, 'node', ['scripts/generate-v44-packs-portfolio-market-intelligence.mjs', '--check']); + } catch { + failures.push('V44 Packs portfolio market intelligence artifact must be fresh.'); + } + + if (!args.skipPackageTests) { + try { + run(root, 'pnpm', ['--dir', 'packages/protocol', 'exec', 'node', '--test', '--test-force-exit', 'test/v44-packs-portfolio-market-intelligence.test.js']); + } catch { + failures.push('packages/protocol/test/v44-packs-portfolio-market-intelligence.test.js must pass.'); + } + } + + if (!args.skipUapiTests) { + try { + run(root, 'pnpm', ['--dir', 'uapi', 'exec', 'jest', 'packActivityModel.test.ts', '--runInBand']); + } catch { + failures.push('uapi packActivityModel.test.ts must pass.'); + } + } + + if (failures.length > 0) { + process.stderr.write('V44 Gate 3 packs portfolio market intelligence check failed:\n'); + for (const failure of failures.filter(Boolean)) process.stderr.write(`- ${failure}\n`); + process.exitCode = 1; + return; + } + + process.stdout.write('V44 Gate 3 packs portfolio market intelligence 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-packs-portfolio-market-intelligence.mjs b/scripts/generate-v44-packs-portfolio-market-intelligence.mjs new file mode 100644 index 00000000..c62145d1 --- /dev/null +++ b/scripts/generate-v44-packs-portfolio-market-intelligence.mjs @@ -0,0 +1,30 @@ +#!/usr/bin/env node + +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { + V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ARTIFACT_PATH, + buildV44PacksPortfolioMarketIntelligence, +} from '../packages/protocol/src/canonical/v44-packs-portfolio-market-intelligence.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const repoRoot = path.resolve(__dirname, '..'); +const checkOnly = process.argv.includes('--check'); +const artifact = buildV44PacksPortfolioMarketIntelligence({ repoRoot }); +const serialized = `${JSON.stringify(artifact, null, 2)}\n`; +const artifactPath = path.join(repoRoot, V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ARTIFACT_PATH); + +if (checkOnly) { + if (!existsSync(artifactPath) || readFileSync(artifactPath, 'utf8') !== serialized) { + process.stderr.write( + `${V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ARTIFACT_PATH} is stale. Run pnpm run generate:v44-packs-portfolio-market-intelligence.\n`, + ); + process.exitCode = 1; + } +} else { + mkdirSync(path.dirname(artifactPath), { recursive: true }); + writeFileSync(artifactPath, serialized); + process.stdout.write(`wrote ${V44_PACKS_PORTFOLIO_MARKET_INTELLIGENCE_ARTIFACT_PATH}\n`); +} diff --git a/uapi/app/api/packs/activity/route.ts b/uapi/app/api/packs/activity/route.ts index d20f00e2..a80cb8ca 100644 --- a/uapi/app/api/packs/activity/route.ts +++ b/uapi/app/api/packs/activity/route.ts @@ -4,6 +4,7 @@ import { GET as getActivity } from '@/app/api/activity/route'; import { assertPackActivitySourceSafe, buildPackActivityDetailProjection, + buildPackPortfolioMarketIntelligence, normalizePackActivityRecord, queryPackActivityRecords, summarizePackActivityRecords, @@ -103,12 +104,14 @@ export async function GET(request: Request) { : records[0] || null; const detail = selected ? buildPackActivityDetailProjection(selected) : null; const safeRecords = records.filter(assertPackActivitySourceSafe); + const marketIntelligence = buildPackPortfolioMarketIntelligence(safeRecords); return NextResponse.json({ ok: true, records: safeRecords, detail: detail && assertPackActivitySourceSafe(detail) ? detail : null, summary: summarizePackActivityRecords(safeRecords), + marketIntelligence, query: query.query, sourceSafety: { sourceSafeMetadataOnly: true, diff --git a/uapi/app/packs/PacksPageClient.tsx b/uapi/app/packs/PacksPageClient.tsx index 88ab3742..bec6f0b4 100644 --- a/uapi/app/packs/PacksPageClient.tsx +++ b/uapi/app/packs/PacksPageClient.tsx @@ -4,10 +4,13 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; import { ArrowDownWideNarrow, ArrowUpWideNarrow, + Building2, + LineChart, Package, RefreshCw, Search, ShieldCheck, + SlidersHorizontal, } from "lucide-react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; @@ -22,6 +25,7 @@ import type { PackActivityType, PackActivitySortDirection, PackActivitySortKey, + PackPortfolioMarketIntelligence, } from "@/components/base/bitcode/activity/pack-activity-model"; type PacksActivityPayload = { @@ -29,6 +33,7 @@ type PacksActivityPayload = { records: PackActivityRecord[]; detail: PackActivityDetailProjection | null; summary: PackActivitySummary; + marketIntelligence: PackPortfolioMarketIntelligence; error?: string; }; @@ -79,6 +84,10 @@ function formatCount(value: number) { ); } +function formatSats(value: number) { + return `${new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 }).format(value)} sats`; +} + function formatType(value: PackActivityType) { return TYPE_OPTIONS.find((option) => option.value === value)?.label || value; } @@ -123,6 +132,8 @@ export default function PacksPageClient() { null, ); const [summary, setSummary] = useState(null); + const [marketIntelligence, setMarketIntelligence] = + useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); @@ -175,10 +186,12 @@ export default function PacksPageClient() { setRecords(payload.records || []); setDetail(payload.detail || null); setSummary(payload.summary || null); + setMarketIntelligence(payload.marketIntelligence || null); } catch (loadError) { setRecords([]); setDetail(null); setSummary(null); + setMarketIntelligence(null); setError( loadError instanceof Error ? loadError.message @@ -211,18 +224,148 @@ export default function PacksPageClient() { tone="emerald" label="Packs" title="Pack activity" - summary="AssetPacks, proof roots, settlement, compensation, delivery, repair." + summary="Portfolio positions, market signals, proof roots, settlement, compensation, delivery, repair." icon={Package} metrics={[ { label: "Rows", value: formatCount(summary?.total || records.length) }, + { + label: "Positions", + value: formatCount(marketIntelligence?.positions.length || 0), + }, + { + label: "Signals", + value: formatCount(marketIntelligence?.signals.length || 0), + }, { label: "Settlement", value: formatCount(summary?.settlementReady || 0), }, - { label: "Delivery", value: formatCount(summary?.deliveryReady || 0) }, - { label: "Repair", value: formatCount(summary?.repairOpen || 0) }, ]} > +
+
+
+
+
+ {(marketIntelligence?.positions || []).slice(0, 4).map((position) => ( + + ))} + {!marketIntelligence?.positions.length && ( + + )} +
+
+ +
+
+
+
+ {(marketIntelligence?.signals || []).slice(0, 4).map((signal) => ( + + ))} + {!marketIntelligence?.signals.length && ( + + )} +
+ +
+ {(marketIntelligence?.savedFilters || []).map((filter) => ( + + ))} +
+
+
+
@@ -294,6 +437,26 @@ export default function PacksPageClient() {
+
+ {[ + ["settlementState", "Settlement facet"], + ["compensationState", "Compensation facet"], + ["deliveryState", "Delivery facet"], + ["repairState", "Repair facet"], + ].map(([key, label]) => ( + + writeParams({ [key]: event.currentTarget.value || null }) + } + className="h-10 border border-white/10 bg-black/30 px-3 text-xs text-neutral-200 outline-none placeholder:text-neutral-600 focus:border-emerald-300/45" + placeholder={label} + aria-label={label} + /> + ))} +
+
diff --git a/uapi/components/base/bitcode/activity/pack-activity-model.ts b/uapi/components/base/bitcode/activity/pack-activity-model.ts index 57e6e9c2..022b6c12 100644 --- a/uapi/components/base/bitcode/activity/pack-activity-model.ts +++ b/uapi/components/base/bitcode/activity/pack-activity-model.ts @@ -139,6 +139,72 @@ export interface PackActivitySummary { repairOpen: number; } +export type PackMarketSignalKind = + | 'demand' + | 'supply' + | 'unfit-need' + | 'settlement' + | 'compensation' + | 'delivery' + | 'repair'; + +export interface PackSavedFilterPreset { + id: string; + label: string; + description: string; + query: Record; + signalKind: PackMarketSignalKind | 'portfolio'; +} + +export interface PackPortfolioPositionProjection { + id: string; + organizationView: string; + repository: string; + assetPackTitle: string; + state: string; + activityCount: number; + lastActivityAt: string | null; + valueTotalSats: number; + btdEstimate: number; + proofRootCount: number; + demandSignalCount: number; + supplySignalCount: number; + unfitNeedSignalCount: number; + settlementState: string | null; + compensationState: string | null; + deliveryState: string | null; + repairState: string | null; + sourceSafety: PackActivitySourceSafety; +} + +export interface PackMarketSignalProjection { + id: string; + kind: PackMarketSignalKind; + label: string; + description: string; + strength: number; + state: string; + repository: string | null; + relatedRecordIds: string[]; + proofRoots: PackActivityProofRoot[]; + sourceSafety: PackActivitySourceSafety; +} + +export interface PackPortfolioFacetSummary { + settlement: Record; + compensation: Record; + delivery: Record; + repair: Record; +} + +export interface PackPortfolioMarketIntelligence { + positions: PackPortfolioPositionProjection[]; + signals: PackMarketSignalProjection[]; + savedFilters: PackSavedFilterPreset[]; + facets: PackPortfolioFacetSummary; + sourceSafety: PackActivitySourceSafety; +} + const SOURCE_SAFETY: PackActivitySourceSafety = { sourceSafeMetadataOnly: true, protectedSourceVisible: false, @@ -509,6 +575,52 @@ function buildSearchText(record: PackActivityRecord) { .toLowerCase(); } +function firstValueTotalSats(record: PackActivityRecord) { + return record.values.reduce((total, value) => { + if (value.unit !== 'sats') return total; + const amount = Number(value.amount); + return Number.isFinite(amount) ? total + amount : total; + }, 0); +} + +function firstBtdEstimate(record: PackActivityRecord) { + const measurement = record.measurements.find((entry) => entry.unit === 'BTD'); + const value = Number(measurement?.value ?? 0); + return Number.isFinite(value) ? value : 0; +} + +function isOpenRepairState(value: string | null) { + return Boolean(value && !/(not_required|closed|complete|completed|none)/iu.test(value)); +} + +function inferSignalKinds(record: PackActivityRecord): PackMarketSignalKind[] { + const text = buildSearchText(record); + const kinds = new Set(); + if (record.type === 'read-need-fit-preview' || includesAny(text, ['read demand', 'need demand', 'finding fits'])) { + kinds.add('demand'); + } + if ( + record.type === 'deposit-option' || + record.type === 'depository-assetpack' || + includesAny(text, ['supply opportunity', 'deposit supply', 'depository supply']) + ) { + kinds.add('supply'); + } + if (includesAny(text, ['unfit', 'no worthy fit', 'no_worthy_fit', 'no fit', 'blocked readiness'])) { + kinds.add('unfit-need'); + } + if (record.type === 'settlement' || record.settlementState) kinds.add('settlement'); + if (record.type === 'compensation' || record.compensationState) kinds.add('compensation'); + if (record.type === 'delivery' || record.deliveryState) kinds.add('delivery'); + if (record.type === 'repair' || isOpenRepairState(record.repairState)) kinds.add('repair'); + return [...kinds]; +} + +function incrementFacet(target: Record, value: string | null) { + const key = value || 'not-recorded'; + target[key] = (target[key] || 0) + 1; +} + export function filterPackActivityRecords( records: PackActivityRecord[], filters: PackActivityFilters = {}, @@ -632,6 +744,125 @@ export function summarizePackActivityRecords(records: PackActivityRecord[]): Pac }; } +export function buildPackPortfolioMarketIntelligence( + records: PackActivityRecord[], +): PackPortfolioMarketIntelligence { + const positionsByKey = new Map(); + const signals: PackMarketSignalProjection[] = []; + const facets: PackPortfolioFacetSummary = { + settlement: {}, + compensation: {}, + delivery: {}, + repair: {}, + }; + + for (const record of records.filter(assertPackActivitySourceSafe)) { + incrementFacet(facets.settlement, record.settlementState); + incrementFacet(facets.compensation, record.compensationState); + incrementFacet(facets.delivery, record.deliveryState); + incrementFacet(facets.repair, record.repairState); + + const positionKey = [ + record.repository || 'network', + record.assetPackTitle || record.title, + ].join(':'); + const current = positionsByKey.get(positionKey); + const signalKinds = inferSignalKinds(record); + const lastActivityAt = + !current?.lastActivityAt || compareText(record.timestamp, current.lastActivityAt) > 0 + ? record.timestamp + : current.lastActivityAt; + + positionsByKey.set(positionKey, { + id: `pack-position:${positionKey.toLowerCase().replace(/[^a-z0-9]+/giu, '-')}`, + organizationView: record.scope === 'personal' ? 'personal' : 'network', + repository: record.repository || 'network', + assetPackTitle: record.assetPackTitle || record.title, + state: record.state || current?.state || 'observed', + activityCount: (current?.activityCount || 0) + 1, + lastActivityAt, + valueTotalSats: (current?.valueTotalSats || 0) + firstValueTotalSats(record), + btdEstimate: (current?.btdEstimate || 0) + firstBtdEstimate(record), + proofRootCount: (current?.proofRootCount || 0) + record.proofRoots.length, + demandSignalCount: + (current?.demandSignalCount || 0) + (signalKinds.includes('demand') ? 1 : 0), + supplySignalCount: + (current?.supplySignalCount || 0) + (signalKinds.includes('supply') ? 1 : 0), + unfitNeedSignalCount: + (current?.unfitNeedSignalCount || 0) + (signalKinds.includes('unfit-need') ? 1 : 0), + settlementState: record.settlementState || current?.settlementState || null, + compensationState: record.compensationState || current?.compensationState || null, + deliveryState: record.deliveryState || current?.deliveryState || null, + repairState: record.repairState || current?.repairState || null, + sourceSafety: SOURCE_SAFETY, + }); + + for (const kind of signalKinds) { + signals.push({ + id: `pack-signal:${kind}:${record.id}`, + kind, + label: normalizeLabel(kind), + description: record.description, + strength: Math.min( + 100, + 20 + record.proofRoots.length * 8 + record.measurements.length * 6 + record.values.length * 6, + ), + state: record.state || record.settlementState || record.compensationState || record.repairState || 'observed', + repository: record.repository, + relatedRecordIds: [record.id], + proofRoots: record.proofRoots.slice(0, 4), + sourceSafety: SOURCE_SAFETY, + }); + } + } + + return { + positions: [...positionsByKey.values()] + .sort((left, right) => compareText(right.lastActivityAt, left.lastActivityAt)) + .slice(0, 24), + signals: signals.sort((left, right) => right.strength - left.strength).slice(0, 32), + savedFilters: [ + { + id: 'portfolio-open-repair', + label: 'Repair cases', + description: 'Open reconciliation and repair states across portfolio positions.', + query: { type: 'repair', repairState: 'open_reconciliation' }, + signalKind: 'repair', + }, + { + id: 'market-demand', + label: 'Demand signals', + description: 'Read Need and Finding Fits activity that indicates buyer demand.', + query: { type: 'read-need-fit-preview' }, + signalKind: 'demand', + }, + { + id: 'market-supply', + label: 'Supply signals', + description: 'Deposit options and admitted Depository AssetPacks.', + query: { type: 'depository-assetpack' }, + signalKind: 'supply', + }, + { + id: 'economic-settlement', + label: 'Settlement facets', + description: 'Quote, payment, finality, and settlement-state readback.', + query: { sort: 'settlementState' }, + signalKind: 'settlement', + }, + { + id: 'economic-compensation', + label: 'Compensation facets', + description: 'Source-to-shares and contributor compensation readback.', + query: { sort: 'compensationState' }, + signalKind: 'compensation', + }, + ], + facets, + sourceSafety: SOURCE_SAFETY, + }; +} + export function queryPackActivityRecords(records: PackActivityRecord[], query: PackActivityQuery = {}) { const filtered = filterPackActivityRecords(records, query.filters || {}, query.search); const sorted = sortPackActivityRecords(filtered, query.sort || {}); diff --git a/uapi/tests/packActivityModel.test.ts b/uapi/tests/packActivityModel.test.ts index 3bdd68f2..41f8a4f9 100644 --- a/uapi/tests/packActivityModel.test.ts +++ b/uapi/tests/packActivityModel.test.ts @@ -1,6 +1,7 @@ import { assertPackActivitySourceSafe, buildPackActivityDetailProjection, + buildPackPortfolioMarketIntelligence, normalizePackActivityRecord, queryPackActivityRecords, } from '@/components/base/bitcode/activity/pack-activity-model'; @@ -127,4 +128,59 @@ describe('pack-activity-model', () => { expect(JSON.stringify(record)).not.toContain('protected source body'); expect(JSON.stringify(record)).not.toContain('raw provider response'); }); + + it('builds source-safe portfolio positions, saved filters, market signals, and facets', () => { + const records = [ + normalizePackActivityRecord(baseRecord), + normalizePackActivityRecord({ + ...baseRecord, + id: 'pack-activity-supply', + title: 'Deposit option admitted', + summary: 'Supply opportunity admitted to the Depository.', + state: 'completed', + payload: { + type: 'pipeline:deposit-option-admission', + assetPackTitle: 'Auth rollback proof pack', + repositoryFullName: 'engineeredsoftware/ENGI', + admittedCount: 1, + compensationState: 'allocation_ready', + settlementState: 'quote_ready', + deliveryState: 'locked_until_settlement', + sourceToSharesRoot: 'source-to-shares-root', + protectedSource: 'protected source body', + }, + }), + normalizePackActivityRecord({ + ...baseRecord, + id: 'pack-activity-unfit', + title: 'No worthy fit found', + summary: 'Unfit Need signal preserved for future supply opportunity.', + state: 'completed', + payload: { + type: 'pipeline:read-fits', + assetPackTitle: 'Latency repair opportunity', + repositoryFullName: 'engineeredsoftware/ENGI', + fitResult: 'no_worthy_fit', + repairState: 'open_reconciliation', + unfitNeedRoot: 'unfit-need-root', + rawPrompt: 'raw prompt text', + }, + }), + ]; + + const market = buildPackPortfolioMarketIntelligence(records); + + expect(market.positions.length).toBeGreaterThanOrEqual(2); + expect(market.positions[0].sourceSafety.sourceSafeMetadataOnly).toBe(true); + expect(market.savedFilters.map((filter) => filter.id)).toEqual( + expect.arrayContaining(['market-demand', 'market-supply', 'economic-settlement']), + ); + expect(market.signals.map((signal) => signal.kind)).toEqual( + expect.arrayContaining(['demand', 'supply', 'unfit-need', 'settlement', 'compensation']), + ); + expect(market.facets.settlement.quote_ready).toBeGreaterThanOrEqual(1); + expect(market.facets.compensation.allocation_ready).toBe(1); + expect(JSON.stringify(market)).not.toContain('protected source body'); + expect(JSON.stringify(market)).not.toContain('raw prompt text'); + }); });