Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/bitcode-gate-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ jobs:
pnpm --filter @bitcode/pipeline-asset-pack exec jest --config jest.config.cjs --passWithNoTests --forceExit
pnpm --filter @bitcode/pipeline-hosts exec jest --config jest.config.cjs --passWithNoTests --forceExit

- name: Test staged Reading route and Terminal harness contracts
run: |
pnpm --dir uapi exec jest --runTestsByPath \
tests/api/readReviewRoute.test.ts \
tests/api/pipelineHarnessRoute.test.ts \
tests/terminalPipelineHarnessClient.test.ts \
tests/terminalDepositReadWorkbench.test.ts \
tests/pipelineExecutionLogHeader.test.tsx \
--runInBand

- name: Test protocol demonstration
run: npm --prefix protocol-demonstration run test:v28-mvp-qa

Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/v28-canon-promotion.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ jobs:
pnpm --filter @bitcode/pipeline-hosts typecheck
pnpm --filter @bitcode/pipeline-asset-pack exec jest --config jest.config.cjs --passWithNoTests --forceExit
pnpm --filter @bitcode/pipeline-hosts exec jest --config jest.config.cjs --passWithNoTests --forceExit
pnpm --dir uapi exec jest --runTestsByPath \
tests/api/readReviewRoute.test.ts \
tests/api/pipelineHarnessRoute.test.ts \
tests/terminalPipelineHarnessClient.test.ts \
tests/terminalDepositReadWorkbench.test.ts \
tests/pipelineExecutionLogHeader.test.tsx \
--runInBand
git diff --check

- name: Promote BITCODE_SPEC pointer
Expand Down
21 changes: 21 additions & 0 deletions BITCODE_SPEC_V28.md
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,27 @@ fail-closed: if the Need measurement, Fit measurement, fee schedule, wallet
authorization, BTC fee, BTD range, or ledger readback is missing, the Terminal
shows blocked readiness rather than implying settlement.

Gate 4 implementation state:

- `/api/read-review` exposes server-side Read-Need synthesis, resynthesis, and
acceptance actions while preserving the prior Read review boundary. The
response stores prompt input, interpolated source context, parsed Need,
measurement root, review state, feedback, and synthesis telemetry.
- Terminal live Need-Fit execution requires an accepted `bitcode.read.need`
object in addition to Deposit and admitted-Read activity ids. The live
harness request carries `acceptedReadNeed` and
`requireAcceptedReadNeed=true`, so depository candidate recall cannot run
from a raw Read request.
- The AssetPack pipeline package owns the source-safe preview contract:
preview id, AssetPack id, Need root, Fit roots, selected candidate ids, score
band, disclosure policy, access policy id/hash, deterministic Share-to-Fee
quote root, BTD range projection, settlement boundary, and locked/unlocked
read-right state.
- The Vercel Sandbox harness writes that source-safe preview into the exported
evidence artifact before ledger settlement readback. Terminal can stream the
preview, fee quote, range projection, access state, ledger state, and
pull-request target without exposing protected source before settlement.

### Long-running full-profile pipeline gate

The current V28 route-streaming gate uses
Expand Down
19 changes: 19 additions & 0 deletions BITCODE_V28_QA.md
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,25 @@ a synchronous route, raw-prompt Read, or source-leaking preview model.
- `buildShareToFeePreview` provides the initial source-safe quote shape from
accepted Need measurement vector and admitted Fit quality:
`sum(measurement.weight * measurement.volume * admitted_fit_quality)`.
- `/api/read-review` now supports `synthesize_read_need`,
`resynthesize_read_need`, and `accept_read_need` actions. The synthesis
response includes prompt input, interpolated source context, parsed Need,
measurement root, review state, feedback, and telemetry; the acceptance
response returns the only admissible `acceptedReadNeed` for Need-Fit search.
- Terminal live harness requests now include `acceptedReadNeed` and
`requireAcceptedReadNeed=true`. The live fit button remains blocked until a
typed accepted Need is present, even when Deposit and admitted-Read activity
rows exist.
- `buildAssetPackSourceSafePreview` records preview id, AssetPack id, Need/Fit
roots, selected candidate ids, score band, deterministic Share-to-Fee quote
root, BTD range projection, disclosure policy, access policy id/hash,
settlement boundary, and read-right state. The Vercel Sandbox harness exports
this object into evidence for Terminal stream readback.
- Focused validation added for this gate:
`pnpm --filter @bitcode/pipeline-asset-pack exec jest --config jest.config.cjs --runTestsByPath src/__tests__/read-need.test.ts --runInBand`,
`pnpm --filter @bitcode/pipeline-hosts exec jest --config jest.config.cjs --runTestsByPath src/__tests__/asset-pack-harness.test.ts --runInBand`,
and
`pnpm --dir uapi exec jest --runTestsByPath tests/api/readReviewRoute.test.ts tests/api/pipelineHarnessRoute.test.ts tests/terminalPipelineHarnessClient.test.ts tests/terminalDepositReadWorkbench.test.ts tests/pipelineExecutionLogHeader.test.tsx --runInBand`.

Observed staging-testnet harness evidence on 2026-05-17:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ describe('asset-pack sandbox harness plan', () => {
expect(diagnostics.filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)).toEqual([]);
expect(source).toContain('pipeline-stream-event');
expect(source).toContain('synthesizeReadNeedForPipelineInput');
expect(source).toContain('buildAssetPackSourceSafePreview');
expect(source).toContain('acceptedReadNeed: readNeed');
expect(source).toContain('requireAcceptedReadNeed');
expect(source).toContain('artifact-streaming-enabled');
Expand All @@ -180,6 +181,8 @@ describe('asset-pack sandbox harness plan', () => {
expect(source).toContain('toolOutputPresent');
expect(source).toContain('toolErrorPresent');
expect(source).toContain('Pipeline produced ');
expect(source).toContain('sourceSafePreview,');
expect(source).toContain("execution.store('asset-pack/preview', 'feeQuote'");
expect(source).toContain('ledgerSettlement,');
});
});
14 changes: 13 additions & 1 deletion packages/pipeline-hosts/src/asset-pack-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1499,7 +1499,7 @@ try {
manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
manifestRoot = createHash('sha256').update(JSON.stringify(manifest)).digest('hex');
userId = process.env.BITCODE_PIPELINE_USER_ID || manifest.deposit?.userId || DEFAULT_USER_ID;
const [{ assetPackPipeline, acceptReadNeed, isAcceptedReadNeed, synthesizeReadNeedForPipelineInput }, { enablePipelineStreaming, factoryPipelineExecution }] = await Promise.all([
const [{ assetPackPipeline, acceptReadNeed, buildAssetPackSourceSafePreview, isAcceptedReadNeed, synthesizeReadNeedForPipelineInput }, { enablePipelineStreaming, factoryPipelineExecution }] = await Promise.all([
import('../../packages/pipelines/asset-pack/src/index'),
import('../../packages/pipelines-generics/src/index'),
]);
Expand Down Expand Up @@ -1609,6 +1609,16 @@ try {
resultState = manifest.sourceOverlay ? 'blocked_readiness' : settlementResultState;
const fitResult = output?.fitResult || output?.fit || null;
const depositorySearch = output?.depositorySearch || null;
const sourceSafePreview = buildAssetPackSourceSafePreview({
need: readNeed,
fitResult,
assetPackId: output?.assetPack?.assetPackId || output?.assetPackId || null,
proofRoot: output?.assetPack?.proofRoot || output?.proofRoot || null,
sourceManifestRoot: output?.assetPack?.sourceManifestRoot || output?.sourceManifestRoot || null,
pullRequestTarget: pullRequestUrl || null,
});
execution.store('asset-pack/preview', 'sourceSafe', sourceSafePreview);
execution.store('asset-pack/preview', 'feeQuote', sourceSafePreview.feeQuote);
const pipelineResultReasons = Array.isArray(fitResult?.resultReasons)
? fitResult.resultReasons
: Array.isArray(depositorySearch?.resultReasons)
Expand Down Expand Up @@ -1643,6 +1653,7 @@ try {
const ledgerSettlement = await settleAssetPackLedger(settlementResultState);
output = {
...(output || {}),
sourceSafePreview,
ledgerSettlement,
};
resultReasons.push(ledgerSettlement.reason);
Expand Down Expand Up @@ -1671,6 +1682,7 @@ try {
output,
fitResult,
depositorySearch,
sourceSafePreview,
assetPackSynthesisArtifacts: output?.assetPackSynthesisArtifacts || null,
writtenAssets: output?.writtenAssets || null,
deliveryMechanism: output?.deliveryMechanism || null,
Expand Down
87 changes: 86 additions & 1 deletion packages/pipelines/asset-pack/src/__tests__/read-need.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import {
acceptReadNeed,
admitNeedFitSearch,
buildAssetPackSourceSafePreview,
buildShareToFeePreview,
isAcceptedReadNeed,
readNeedToDepositorySearchRead,
resolveAssetPackReadRightState,
synthesizeReadNeedForPipelineInput,
} from '../read-need';
import { runDepositorySearchForPipelineInput } from '../depository-search';
import {
buildDepositoryFitResultEvidence,
runDepositorySearchForPipelineInput,
} from '../depository-search';

const input = {
read: {
Expand Down Expand Up @@ -136,6 +141,86 @@ describe('Read-Need synthesis and Need-Fit admission', () => {
finalityState: 'preview_not_paid',
payer: 'reader',
});
expect(preview.feeSchedule).toMatchObject({
satsPerWeightedVolume: 1000,
minimumSats: 546,
dustFloorSats: 546,
networkFeePosture: 'reader_wallet_authorized_before_broadcast',
});
expect(preview.measurementVector).toBe(acceptedReadNeed.pricingMeasurementInputs.measurementVector);
expect(preview.quoteRoot).toMatch(/^sha256:/);
expect(preview.sats).toBeGreaterThanOrEqual(546);
});

it('builds a source-safe AssetPack preview without unlocking protected source', async () => {
const acceptedReadNeed = acceptReadNeed(
synthesizeReadNeedForPipelineInput(input),
'2026-05-18T00:00:00.000Z'
);
const search = await runDepositorySearchForPipelineInput({
...input,
acceptedReadNeed,
requireAcceptedReadNeed: true,
depositoryAssets: [depositoryAsset()],
});
const fitResult = buildDepositoryFitResultEvidence(search);
const preview = buildAssetPackSourceSafePreview({
need: acceptedReadNeed,
fitResult,
rangeStart: 42,
pullRequestTarget: 'https://github.com/engineeredsoftware/ENGI/pull/6',
createdAt: '2026-05-18T00:00:00.000Z',
});

expect(preview).toMatchObject({
schema: 'bitcode.asset-pack.source-safe-preview',
need: {
needId: acceptedReadNeed.needId,
measurementRoot: acceptedReadNeed.measurementRoot,
reviewState: 'accepted',
},
fit: {
resultState: 'worthy_fit',
selectedCandidateAssetIds: ['deposit-asset-1'],
},
disclosurePolicy: {
protectedSourceDisclosure: 'forbidden_before_settlement',
},
settlementBoundary: {
payer: 'reader',
payee: 'depositor',
serverCustody: false,
ownershipTruth: 'asset_pack_range_and_license_rows',
},
unlock: {
state: 'pending_settlement',
sourceAvailable: false,
},
delivery: {
pullRequestTarget: 'https://github.com/engineeredsoftware/ENGI/pull/6',
availableAfterSettlement: true,
},
});
expect(preview.roots.previewRoot).toMatch(/^sha256:/);
expect(preview.roots.sourceManifestRoot).toMatch(/^sha256:/);
expect(preview.feeQuote.quoteRoot).toMatch(/^sha256:/);
expect(preview.rangeProjection).toMatchObject({
status: 'projected_not_minted',
rangeStart: 42,
});
expect(preview.rangeProjection.rangeEndExclusive).toBe(
preview.rangeProjection.rangeStart! + preview.rangeProjection.tokenCount
);
expect(preview.disclosurePolicy.withheldBeforeSettlement).toContain('protected source content');
});

it('keeps owner, licensed, pending-settlement, and denied read rights distinct', () => {
expect(resolveAssetPackReadRightState({ hasOwnerClaim: true })).toBe('owner_read');
expect(resolveAssetPackReadRightState({
hasReadLicense: true,
settlementReadbackComplete: true,
})).toBe('licensed_read');
expect(resolveAssetPackReadRightState({ settlementPending: true })).toBe('pending_settlement');
expect(resolveAssetPackReadRightState({ policyDenied: true })).toBe('denied');
});
});
5 changes: 5 additions & 0 deletions packages/pipelines/asset-pack/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,16 +277,21 @@ export { AssetPackSynthesizeArtifactsAgent } from './agents/implementation/asset
export {
acceptReadNeed,
admitNeedFitSearch,
buildAssetPackSourceSafePreview,
buildShareToFeePreview,
isAcceptedReadNeed,
readNeedToDepositorySearchRead,
resolveAssetPackReadRightState,
resolveReadNeedFromPipelineInput,
shouldRequireAcceptedReadNeed,
synthesizeReadNeedForPipelineInput,
type AssetPackReadRightState,
type AssetPackSourceSafePreview,
type ReadNeed,
type ReadNeedFitAdmission,
type ReadNeedMeasurementDimension,
type ReadNeedReviewState,
type ShareToFeeQuote,
} from './read-need';
export default assetPackPipeline;
export const runSDIVFPipeline = assetPackPipeline;
Loading
Loading