@tiinex/core 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/package.json +7 -6
  2. package/src/integrity/integrity.c14nV1.js +28 -0
  3. package/src/lineage/lineage.integrity.js +108 -22
  4. package/src/lineage/lineage.resolve.js +21 -14
  5. package/src/lineage/lineage.sourceScope.js +41 -12
  6. package/src/lineage/lineage.targetKeys.js +26 -7
  7. package/src/tooling/portable/adapters/cli/cli.command-input.js +11 -0
  8. package/src/tooling/portable/adapters/cli/cli.ground-materialize.js +2 -0
  9. package/src/tooling/portable/adapters/cli/cli.handoff-manufacture.js +4 -1
  10. package/src/tooling/portable/adapters/cli/cli.help.js +1 -1
  11. package/src/tooling/portable/adapters/cli/cli.land.js +23 -1
  12. package/src/tooling/portable/adapters/cli/cli.run.js +1 -1
  13. package/src/tooling/portable/adapters/node/handoff.manufacture.js +9 -1
  14. package/src/tooling/portable/handoff/carrierProjection.js +2 -2
  15. package/src/tooling/portable/handoff/carrierProjection.routeQualification.js +1 -0
  16. package/src/tooling/portable/handoff/delegationReturnReservation.js +101 -0
  17. package/src/tooling/portable/handoff/manufacture.js +7 -1
  18. package/src/tooling/portable/handoff/materialClosure.archiveV2.js +1 -1
  19. package/src/tooling/portable/handoff/recipientV2.artifactInspection.js +11 -0
  20. package/src/tooling/portable/handoff/recipientV2.inspect.projection.js +7 -3
  21. package/src/tooling/portable/handoff/recipientV2.packageV1.build.js +3 -2
  22. package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.helpers.js +7 -1
  23. package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.js +15 -2
  24. package/src/tooling/portable/handoff/recipientV2.pointer.js +2 -0
  25. package/src/tooling/portable/handoff/recoveryAcceptanceAudit.js +145 -0
  26. package/src/tooling/portable/handoff/workspaceLandingPlan.js +110 -13
  27. package/src/tooling/portable/operation.catalog.package.js +9 -1
@@ -9,6 +9,7 @@ import { enumerateNodeWorkspace, PORTABLE_NODE_WORKSPACE_ENUMERATION_SCHEMA_ID }
9
9
  import { preparePackageParentWorkspaceReuse } from './handoff.manufacture.packageParent.js';
10
10
  import { qualifyPortableSourceReconciliationProofForManufacture } from '../../comparison/sourceFrontierReconciliationProof.js';
11
11
  import { qualifyPortableManufactureSchemaReferenceCandidate } from '../../handoff/schemaReferencePreflight.js';
12
+ import { qualifyDelegationReturnReservation } from '../../handoff/delegationReturnReservation.js';
12
13
  import {
13
14
  assertInside,
14
15
  expandPointerDependencyClosure,
@@ -102,6 +103,7 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
102
103
  });
103
104
 
104
105
  const schemaReferencePreflight = qualifyPortableManufactureSchemaReferenceCandidate(handoff);
106
+ const returnCarrierReservationPreflight = qualifyDelegationReturnReservation({ markdown: handoffMarkdown, returnPackageSiblingIndex: input.returnPackageSiblingIndex, returnPackageMajor: input.returnPackageMajor === true });
105
107
 
106
108
  if (enumeration.status !== 'qualified-complete') throw new Error(`portable.handoff-manufacture.workspace-enumeration.${enumeration.status}`);
107
109
  const workspaceTitle = requestedWorkspaceTitle || inferWorkspaceTitle(enumeration) || workspaceId;
@@ -128,7 +130,11 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
128
130
  if (!id || workspaceRuntimeById.has(id)) continue;
129
131
  workspaceRuntimeById.set(id, Object.freeze({ id, root: '', enumeration: provided.enumeration, provider: 'qualified-package-parent-workspace-material-provider' }));
130
132
  }
131
- const transportRoutes = Object.freeze([...(input.transportRoutes || input.handoffRoutes || [])].map((route) => normalizeTransportRoute(route, workspaceId)).filter(Boolean));
133
+ const suppliedTransportRoutes = [...(input.transportRoutes || input.handoffRoutes || [])].map((route) => normalizeTransportRoute(route, workspaceId)).filter(Boolean);
134
+ const reservationProjection = returnCarrierReservationPreflight.state === 'qualified' && returnCarrierReservationPreflight.returnExpected
135
+ ? Object.freeze({ carrierKind: returnCarrierReservationPreflight.carrierKind, siblingIndex: returnCarrierReservationPreflight.siblingIndex })
136
+ : null;
137
+ const transportRoutes = Object.freeze((suppliedTransportRoutes.length ? suppliedTransportRoutes : [Object.freeze({ workspaceId, path: handoffPath })]).map((route) => Object.freeze({ ...route, ...(route.path === handoffPath && String(route.workspaceId || '') === workspaceId && reservationProjection ? { returnCarrierReservation: reservationProjection } : {}) })));
132
138
  const workspaceTargets = mergeWorkspaceTargetBindings(normalizeWorkspaceTargetBindings({
133
139
  primaryWorkspaceId: workspaceId,
134
140
  primaryTargetPath: input.workspaceTargetPath || input.workspaceArtifactPath || '',
@@ -195,6 +201,7 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
195
201
  toolingBootstrap: toolingBootstrap.summary,
196
202
  reconciliationProofQualification,
197
203
  schemaReferencePreflight,
204
+ returnCarrierReservationPreflight,
198
205
  manufacturingEvidence: Object.freeze({
199
206
  enumeration: enumeration.evidence,
200
207
  workspaceEnumerations: Object.freeze(workspaceEnumerations),
@@ -202,6 +209,7 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
202
209
  runtimeSourceAlignment,
203
210
  reconciliationProof: reconciliationProofQualification,
204
211
  schemaReferencePreflight,
212
+ returnCarrierReservationPreflight,
205
213
  packageParentWorkspaceReuse: Object.freeze({
206
214
  state: String(packageParentReuse.state || ''),
207
215
  providerState: String(packageParentReuse.providerState || ''),
@@ -58,7 +58,7 @@ export function inspectHandoffCarrierProjection(bundle = {}, options = {}) {
58
58
  if (projection && projection.schema !== HANDOFF_CARRIER_PROJECTION_SCHEMA_ID) findings.push(finding('error', 'portable.handoff-carrier.schema.invalid', 'Handoff carrier projection schema/version is unsupported.'));
59
59
  if (projection && projection.boundary !== BOUNDARY) findings.push(finding('error', 'portable.handoff-carrier.boundary.invalid', 'Handoff carrier projection lost its disposable non-authoritative boundary.'));
60
60
  if (projection) {
61
- const expected = buildHandoffCarrierProjection({ bundle, workspaceByteProvider: options.workspaceByteProvider || null, carrierLineage: projection.lineage || null, routes: (projection.routes || []).map((route) => ({ workspaceId: route.workspaceId, path: route.workspaceRelativePath, purpose: route.purpose, participantRoles: route.participantRoleSpecs || [] })) });
61
+ const expected = buildHandoffCarrierProjection({ bundle, workspaceByteProvider: options.workspaceByteProvider || null, carrierLineage: projection.lineage || null, routes: (projection.routes || []).map((route) => ({ workspaceId: route.workspaceId, path: route.workspaceRelativePath, purpose: route.purpose, participantRoles: route.participantRoleSpecs || [], returnCarrierReservation: route.returnCarrierReservation || null })) });
62
62
  for (const field of ['status', 'mode', 'lineage', 'workspaces', 'workspace', 'selection', 'routes', 'authority']) {
63
63
  if (stableJson(expected[field]) !== stableJson(projection[field])) findings.push(finding('error', `portable.handoff-carrier.${field}.mismatch`, `Handoff carrier ${field} diverges from current package/workspace truth.`));
64
64
  }
@@ -162,7 +162,7 @@ function normalizeRouteSpecs(value, descriptor, defaultWorkspace = null) {
162
162
  const path = normalizeWorkspacePath(spec.path || spec.workspaceRelativePath || '');
163
163
  const workspaceId = String(spec.workspaceId || spec.workspace || defaultWorkspaceId || '');
164
164
  const key = `${workspaceId}\u0000${path}`;
165
- if (path && !map.has(key)) map.set(key, Object.freeze({ workspaceId, path, purpose: String(spec.purpose || ''), participantRoles: Object.freeze([...(spec.participantRoles || spec.roles || [])].map((entry) => typeof entry === 'string' ? entry : Object.freeze({ ...(entry || {}) }))) }));
165
+ if (path && !map.has(key)) map.set(key, Object.freeze({ workspaceId, path, purpose: String(spec.purpose || ''), participantRoles: Object.freeze([...(spec.participantRoles || spec.roles || [])].map((entry) => typeof entry === 'string' ? entry : Object.freeze({ ...(entry || {}) }))), returnCarrierReservation: spec.returnCarrierReservation ? Object.freeze({ ...(spec.returnCarrierReservation || {}) }) : null }));
166
166
  }
167
167
  return [...map.values()].sort((a, b) => a.workspaceId.localeCompare(b.workspaceId) || a.path.localeCompare(b.path));
168
168
  }
@@ -53,6 +53,7 @@ export function qualifyRoute(bundle, descriptor, byteProvider, workspace, spec =
53
53
  materialRequirements,
54
54
  participantRoles,
55
55
  participantRoleSpecs,
56
+ returnCarrierReservation: spec.returnCarrierReservation ? deepFreeze({ ...(spec.returnCarrierReservation || {}) }) : null,
56
57
  requiredClosure,
57
58
  reasons: Object.freeze(reasons),
58
59
  authority: Object.freeze({ artifactPartiesAuthoritative: true, dimensionSemanticAuthority: false, filenameSemanticAuthority: false })
@@ -0,0 +1,101 @@
1
+ export const PORTABLE_DELEGATION_RETURN_RESERVATION_SCHEMA_ID = 'tiinex.portable.delegation-return-reservation-preflight.v1';
2
+
3
+ export function qualifyDelegationReturnReservation(input = {}) {
4
+ const markdown = String(input.markdown || input.handoffMarkdown || '');
5
+ const completion = sectionText(markdown, 'Completion Expectation');
6
+ const signalKind = field(completion, 'Signal Kind').toLowerCase();
7
+ const returnExpected = signalKind === 'return';
8
+ const declaredMajor = input.returnPackageMajor === true;
9
+ const rawIndex = firstDefined(input.returnPackageSiblingIndex, reservedSiblingIndexFromHandoff(markdown));
10
+ const findings = [];
11
+
12
+ if (!returnExpected) return freeze({
13
+ schema: PORTABLE_DELEGATION_RETURN_RESERVATION_SCHEMA_ID,
14
+ state: 'not-required',
15
+ returnExpected: false,
16
+ carrierKind: 'not-applicable',
17
+ siblingIndex: null,
18
+ findings,
19
+ boundary: boundary()
20
+ });
21
+
22
+ if (declaredMajor) {
23
+ if (rawIndex !== undefined && rawIndex !== null && String(rawIndex).trim() !== '') findings.push(finding('error', 'portable.delegation-return-reservation.major-index-conflict', 'A Major return declaration must not also carry a non-Major package sibling index.'));
24
+ return freeze({
25
+ schema: PORTABLE_DELEGATION_RETURN_RESERVATION_SCHEMA_ID,
26
+ state: findings.length ? 'blocked' : 'qualified',
27
+ returnExpected: true,
28
+ carrierKind: 'major',
29
+ siblingIndex: null,
30
+ findings,
31
+ boundary: boundary()
32
+ });
33
+ }
34
+
35
+ const siblingIndex = parseSiblingIndex(rawIndex);
36
+ if (siblingIndex === null) findings.push(finding('error', 'portable.delegation-return-reservation.sibling-index.required', 'A return Handoff is transport-not-ready until the delegator supplies one explicit non-Major return package sibling index in the supported range 1..9999.'));
37
+ return freeze({
38
+ schema: PORTABLE_DELEGATION_RETURN_RESERVATION_SCHEMA_ID,
39
+ state: findings.length ? 'blocked' : 'qualified',
40
+ returnExpected: true,
41
+ carrierKind: 'non-major',
42
+ siblingIndex,
43
+ findings,
44
+ boundary: boundary()
45
+ });
46
+ }
47
+
48
+ export function parseDelegationReturnReservationPointer(pointer = {}) {
49
+ const siblingIndex = parseSiblingIndex(pointer.returnPackageSiblingIndex);
50
+ const carrierKind = String(pointer.returnPackageCarrierKind || '').trim().toLowerCase();
51
+ if (carrierKind === 'major') return freeze({ state: 'qualified', returnExpected: true, carrierKind: 'major', siblingIndex: null });
52
+ if (carrierKind === 'non-major' && siblingIndex !== null) return freeze({ state: 'qualified', returnExpected: true, carrierKind: 'non-major', siblingIndex });
53
+ return freeze({ state: 'unresolved', returnExpected: false, carrierKind: '', siblingIndex: null });
54
+ }
55
+
56
+
57
+ function reservedSiblingIndexFromHandoff(markdown = '') {
58
+ const transfers = sectionText(markdown, 'Transfers');
59
+ if (!transfers) return null;
60
+ const lines = transfers.replace(/\r\n?/g, '\n').split('\n');
61
+ const start = lines.findIndex((line) => /^\s*-\s+reserved-return-package-sibling-index\s*$/i.test(line));
62
+ if (start < 0) return null;
63
+ const block = [];
64
+ for (let index = start + 1; index < lines.length; index += 1) {
65
+ if (/^-\s+[^\s].*$/.test(lines[index])) break;
66
+ block.push(lines[index]);
67
+ }
68
+ const description = field(block.join('\n'), 'Description');
69
+ const match = description.match(/(?:package\s+sibling\s+index|sibling\s+index|index)\s+`?(\d{1,4})`?/i);
70
+ return match ? match[1] : null;
71
+ }
72
+ function firstDefined(...values) {
73
+ for (const value of values) if (value !== undefined && value !== null && String(value).trim() !== '') return value;
74
+ return null;
75
+ }
76
+
77
+ function parseSiblingIndex(value) {
78
+ if (value === undefined || value === null || String(value).trim() === '') return null;
79
+ if (!/^\d+$/.test(String(value).trim())) return null;
80
+ const parsed = Number.parseInt(String(value).trim(), 10);
81
+ return Number.isInteger(parsed) && parsed >= 1 && parsed <= 9999 ? parsed : null;
82
+ }
83
+ function sectionText(markdown = '', heading = '') {
84
+ const lines = String(markdown || '').replace(/\r\n?/g, '\n').split('\n');
85
+ const start = lines.findIndex((line) => new RegExp(`^##\\s+${escapeRegExp(heading)}\\s*$`, 'i').test(line));
86
+ if (start < 0) return '';
87
+ const out = [];
88
+ for (let index = start + 1; index < lines.length; index += 1) {
89
+ if (/^##\s+/.test(lines[index])) break;
90
+ out.push(lines[index]);
91
+ }
92
+ return out.join('\n');
93
+ }
94
+ function field(section = '', name = '') {
95
+ const match = String(section || '').match(new RegExp(`^\\s*-\\s+${escapeRegExp(name)}\\s*:\\s*(.+?)\\s*$`, 'mi'));
96
+ return String(match?.[1] || '').trim();
97
+ }
98
+ function boundary() { return 'Transport preflight only. It validates an explicit delegator-coordinated return reservation and never allocates, discovers, increments, guesses, recycles, or promotes a sibling index into semantic Parent, Workspace, Role, acceptance, or completion authority.'; }
99
+ function finding(severity, code, message) { return Object.freeze({ severity, code, message }); }
100
+ function escapeRegExp(value = '') { return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
101
+ function freeze(value) { if (Array.isArray(value)) return Object.freeze(value.map(freeze)); if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value; return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, freeze(item)]))); }
@@ -21,9 +21,12 @@ export function manufactureRecipientRelativeHandoffPackage(input = {}, options =
21
21
  const reconciliationBlocked = String(reconciliationProofQualification?.state || '') === 'blocked';
22
22
  const schemaReferencePreflight = input.schemaReferencePreflight || input.manufacturingEvidence?.schemaReferencePreflight || null;
23
23
  const schemaReferenceBlocked = String(schemaReferencePreflight?.state || '') === 'blocked';
24
+ const returnCarrierReservationPreflight = input.returnCarrierReservationPreflight || input.manufacturingEvidence?.returnCarrierReservationPreflight || null;
25
+ const returnCarrierReservationBlocked = String(returnCarrierReservationPreflight?.state || '') === 'blocked';
24
26
  const findings = Object.freeze([
25
27
  ...majorFindings,
26
28
  ...(schemaReferencePreflight?.findings || []),
29
+ ...(returnCarrierReservationPreflight?.findings || []),
27
30
  ...(reconciliationProofQualification?.findings || []),
28
31
  ...(baseline.findings || []),
29
32
  ...(upgraded.findings || []),
@@ -36,7 +39,7 @@ export function manufactureRecipientRelativeHandoffPackage(input = {}, options =
36
39
  ...(upgraded.roundtrip?.findings || []),
37
40
  ...(toolingBootstrapInspection?.findings || [])
38
41
  ]);
39
- const status = baseline.status !== 'blocked' && upgraded.status !== 'blocked' && toolingBootstrapInspection?.status === 'valid' && majorReadiness.state !== 'blocked' && !schemaReferenceBlocked && !reconciliationBlocked ? upgraded.status : 'blocked';
42
+ const status = baseline.status !== 'blocked' && upgraded.status !== 'blocked' && toolingBootstrapInspection?.status === 'valid' && majorReadiness.state !== 'blocked' && !schemaReferenceBlocked && !returnCarrierReservationBlocked && !reconciliationBlocked ? upgraded.status : 'blocked';
40
43
  return Object.freeze({
41
44
  schema: 'tiinex.portable.handoff-manufacturing.v2',
42
45
  status,
@@ -55,6 +58,7 @@ export function manufactureRecipientRelativeHandoffPackage(input = {}, options =
55
58
  roundtrip: upgraded.roundtrip ? String(upgraded.roundtrip.status || 'unknown') : 'not-requested',
56
59
  toolingBootstrap: String(toolingBootstrapInspection?.status || 'unavailable'),
57
60
  schemaReferencePreflight: String(schemaReferencePreflight?.state || 'not-run'),
61
+ returnCarrierReservationPreflight: String(returnCarrierReservationPreflight?.state || 'not-run'),
58
62
  reconciliationProof: String(reconciliationProofQualification?.state || 'not-required')
59
63
  }),
60
64
  plan: baseline.plan,
@@ -74,6 +78,7 @@ export function manufactureRecipientRelativeHandoffPackage(input = {}, options =
74
78
  toolingBootstrap: input.toolingBootstrap || null,
75
79
  manufacturingEvidence: input.manufacturingEvidence || null,
76
80
  schemaReferencePreflight,
81
+ returnCarrierReservationPreflight,
77
82
  reconciliationProofQualification,
78
83
  toolingBootstrapInspection,
79
84
  carrierLineage: upgraded.carrierProjection?.lineage || baseline.carrierProjection?.lineage || input.carrierLineage || null,
@@ -86,6 +91,7 @@ export function manufactureRecipientRelativeHandoffPackage(input = {}, options =
86
91
  remoteMutation: false,
87
92
  physicalRoundtripVerification: upgraded.roundtrip ? String(upgraded.roundtrip.status || 'unknown') : 'not-requested',
88
93
  schemaReferencePreflight: String(schemaReferencePreflight?.state || 'not-run'),
94
+ returnCarrierReservationPreflight: String(returnCarrierReservationPreflight?.state || 'not-run'),
89
95
  reconciliationProof: String(reconciliationProofQualification?.state || 'not-required'),
90
96
  hostBehaviorAuthority: 'none'
91
97
  }),
@@ -115,7 +115,7 @@ export function upgradeRecipientRelativeHandoffTransportPackageV2(baseline = {},
115
115
  const descriptorFile = finalizeFile({ path: HANDOFF_CLOSURE_DESCRIPTOR_PATH, kind: 'handoff-closure-descriptor', logicalKind: 'disposable-transport-control', mediaType: 'application/json', content: `${stablePrettyJson(descriptor)}\n`, boundary: descriptor.boundary });
116
116
  const projectionBundle = { ...baselineBundle, files: [...retained, ...workspaceFiles, descriptorFile], handoffClosure: descriptor };
117
117
  const provider = buildDirectArchiveProjectionProvider(records);
118
- const carrierProjection = buildHandoffCarrierProjection({ bundle: projectionBundle, descriptor, workspaceByteProvider: provider, carrierLineage: input.carrierLineage || baseline.carrierProjection?.lineage || null, routes: input.transportRoutes || input.handoffRoutes || (baseline.carrierProjection?.routes || []).map((route) => ({ workspaceId: route.workspaceId, path: route.workspaceRelativePath, purpose: route.purpose })) });
118
+ const carrierProjection = buildHandoffCarrierProjection({ bundle: projectionBundle, descriptor, workspaceByteProvider: provider, carrierLineage: input.carrierLineage || baseline.carrierProjection?.lineage || null, routes: input.transportRoutes || input.handoffRoutes || (baseline.carrierProjection?.routes || []).map((route) => ({ workspaceId: route.workspaceId, path: route.workspaceRelativePath, purpose: route.purpose, returnCarrierReservation: route.returnCarrierReservation || null })) });
119
119
  const createdAt=baselineBundle.manifest?.createdAt||baselineBundle.builtAt||'';
120
120
  const transportStatus = baseline.status === 'blocked' || carrierProjection.status !== 'ready' ? 'blocked' : baseline.status;
121
121
  const transportCompanion = buildHandoffTransportCompanionProjection({ bundle: projectionBundle, descriptor, packageStatus: transportStatus, participation: input.transportParticipation || input.participation || {} });
@@ -24,6 +24,17 @@ export function correlatePointerFacts(markdown, facts, findings, path) {
24
24
  if (facts.role !== 'handoff-route' || !facts.archivePath) return;
25
25
  const targets = [...sectionText(markdown, 'Destinations').matchAll(/\[[^\]]*\]\(([^)]+)\)/g)].map((match) => match[1]);
26
26
  if (targets.length !== 1 || targets[0] !== String(facts.archivePath)) findings.push(finding('error', 'portable.handoff-v2-surface.pointer.visible-destination-mismatch', 'Route Pointer visible Destination diverges from its sealed machine facts.', { path }));
27
+ if (facts.returnCarrierReservation) {
28
+ const current = sectionText(markdown, 'Current Read');
29
+ const visibleKind = unquoteCode(fieldValue(current, 'Return Package Carrier Kind'));
30
+ const expectedKind = String(facts.returnCarrierReservation.carrierKind || '');
31
+ if (visibleKind !== expectedKind) findings.push(finding('error', 'portable.handoff-v2-surface.pointer.visible-return-carrier-kind-mismatch', 'Route Pointer visible return-carrier kind diverges from its sealed machine facts.', { path }));
32
+ if (expectedKind === 'non-major') {
33
+ const visibleSiblingIndex = Number(unquoteCode(fieldValue(current, 'Return Package Sibling Index')) || 0);
34
+ const expectedSiblingIndex = Number(facts.returnCarrierReservation.siblingIndex || 0);
35
+ if (visibleSiblingIndex !== expectedSiblingIndex) findings.push(finding('error', 'portable.handoff-v2-surface.pointer.visible-return-sibling-index-mismatch', 'Route Pointer visible return package sibling index diverges from its sealed machine facts.', { path }));
36
+ }
37
+ }
27
38
  }
28
39
  export function markdownTarget(value = '') { return String(value || '').match(/\[[^\]]*\]\(([^)]+)\)/)?.[1] || String(value || '').trim(); }
29
40
  export function inspectExternalPayloadShape(markdown, findings, path) {
@@ -1,13 +1,17 @@
1
1
  import { parentTrace } from './recipientV2.lineage.js';
2
2
 
3
- export function projectRecipientV2Routes(routePointers = [], endpointPointers = [], participantPointers = []) {
4
- return Object.freeze(routePointers.map((item) => Object.freeze({
3
+ export function projectRecipientV2Routes(routePointers = [], endpointPointers = [], participantPointers = [], qualifiedRoutes = []) {
4
+ return Object.freeze(routePointers.map((item) => {
5
+ const qualified = (qualifiedRoutes || []).find((route) => String(route.workspaceId || '') === String(item.facts?.workspaceId || '') && String(route.workspaceRelativePath || '') === String(item.facts?.workspaceRelativeHandoffPath || '')) || null;
6
+ return Object.freeze({
5
7
  pointerPath: item.path,
6
8
  workspaceId: String(item.facts?.workspaceId || ''),
7
9
  workspaceRelativeHandoffPath: String(item.facts?.workspaceRelativeHandoffPath || ''),
10
+ returnCarrierReservation: item.facts?.returnCarrierReservation || qualified?.returnCarrierReservation || null,
8
11
  endpointRolePointers: Object.freeze(rolePointerAncestors(item, endpointPointers, participantPointers).filter((path) => endpointPointers.some((pointer) => pointer.path === path))),
9
12
  participantRolePointers: Object.freeze(rolePointerAncestors(item, endpointPointers, participantPointers).filter((path) => participantPointers.some((pointer) => pointer.path === path)))
10
- })));
13
+ });
14
+ }));
11
15
  }
12
16
 
13
17
  export function projectRecipientV2EndpointRoles(pointers = []) {
@@ -173,11 +173,12 @@ function buildRecipientFacingV2PackageV1Prepared(input = {}, sealedByWorkspaceId
173
173
  workspaceId: owningWorkspace.workspaceId, workspaceArtifactPath: owningWorkspace.workspacePath, workspaceArtifactSha256: owningWorkspace.workspaceSha256,
174
174
  archivePath: owningWorkspace.archivePath, archiveSha256: owningWorkspace.archiveSha256, sourceWorkspaceTargetInnerPath: owningWorkspace.sourceWorkspaceTargetInnerPath,
175
175
  sourceWorkspaceTargetSha256: owningWorkspace.sourceWorkspaceTargetSha256, workspaceRelativeHandoffPath: String(route.workspaceRelativePath || ''), handoffBytes: Number(routeEntry?.bytes || 0), handoffSha256: String(route.sha256 || ''), routeId: String(route.id || ''), parties: route.parties || {}, cacheArtifactPath: cache?.artifactPath || '',
176
+ returnCarrierReservation: route.returnCarrierReservation || null,
176
177
  requiredContextBindings: Object.freeze((route.requiredClosure?.requirements || []).filter((entry) => entry.state === 'qualified' && entry.resolution?.kind === 'workspace-archive-entry').map((entry) => Object.freeze({ requirementId: String(entry.requirementId || ''), name: String(entry.name || ''), referenceTarget: String(entry.referenceTarget || ''), workspaceId: String(entry.resolution?.workspaceId || ''), workspaceRelativePath: String(entry.resolution?.workspaceRelativePath || entry.resolution?.innerPath || ''), bytes: Number(entry.resolution?.bytes || 0), sha256: String(entry.resolution?.sha256 || '') })))
177
178
  };
178
- const pointer = finalizeFile({ path: pointerPath, kind: 'handoff-route-pointer', logicalKind: 'recipient-v2-package-v1-handoff-route-pointer', mediaType: 'text/markdown', transportFacts: recipientV2TransportFacts('handoff-route', pointerFacts), content: renderRecipientV2Pointer({ createdAt, parent: lineageParent, role: 'handoff-route', title: `Handoff Route Pointer — ${String(route.parties?.to || owningWorkspace.workspaceId || 'recipient')}`, summary: 'Qualified package-local Pointer to one authoritative Handoff inside one qualified carried Workspace representation.', prose: 'Follow only this Pointer carrier-ancestor closure for pre-Handoff package grounding, then resolve the authoritative Handoff path against the exact qualified carried Workspace representation.', currentRead: [{ label: 'Workspace Id', value: `\`${owningWorkspace.workspaceId}\`` }, { label: 'Workspace', value: `[${owningWorkspace.workspaceId}](${owningWorkspace.workspacePath})` }, { label: 'Route Id', value: `\`${String(route.id || '')}\`` }, ...(pointerFacts.cacheArtifactPath ? [{ label: 'Workspace Dependency Cache', value: `[cache](${pointerFacts.cacheArtifactPath})` }] : []), { label: 'Handoff Workspace Path', value: `\`${String(route.workspaceRelativePath || '')}\`` }], destinations: [{ label: 'Workspace representation containing the qualified Handoff route', display: `${owningWorkspace.archivePath} :: ${String(route.workspaceRelativePath || '')}`, target: owningWorkspace.archivePath }], facts: pointerFacts }) });
179
+ const pointer = finalizeFile({ path: pointerPath, kind: 'handoff-route-pointer', logicalKind: 'recipient-v2-package-v1-handoff-route-pointer', mediaType: 'text/markdown', transportFacts: recipientV2TransportFacts('handoff-route', pointerFacts), content: renderRecipientV2Pointer({ createdAt, parent: lineageParent, role: 'handoff-route', title: `Handoff Route Pointer — ${String(route.parties?.to || owningWorkspace.workspaceId || 'recipient')}`, summary: 'Qualified package-local Pointer to one authoritative Handoff inside one qualified carried Workspace representation.', prose: 'Follow only this Pointer carrier-ancestor closure for pre-Handoff package grounding, then resolve the authoritative Handoff path against the exact qualified carried Workspace representation.', currentRead: [{ label: 'Workspace Id', value: `\`${owningWorkspace.workspaceId}\`` }, { label: 'Workspace', value: `[${owningWorkspace.workspaceId}](${owningWorkspace.workspacePath})` }, { label: 'Route Id', value: `\`${String(route.id || '')}\`` }, ...(pointerFacts.cacheArtifactPath ? [{ label: 'Workspace Dependency Cache', value: `[cache](${pointerFacts.cacheArtifactPath})` }] : []), { label: 'Handoff Workspace Path', value: `\`${String(route.workspaceRelativePath || '')}\`` }, ...(route.returnCarrierReservation ? [{ label: 'Return Package Carrier Kind', value: `\`${String(route.returnCarrierReservation.carrierKind || '')}\`` }, ...(route.returnCarrierReservation.carrierKind === 'non-major' ? [{ label: 'Return Package Sibling Index', value: `\`${String(route.returnCarrierReservation.siblingIndex || '')}\`` }] : [])] : [])], destinations: [{ label: 'Workspace representation containing the qualified Handoff route', display: `${owningWorkspace.archivePath} :: ${String(route.workspaceRelativePath || '')}`, target: owningWorkspace.archivePath }], facts: pointerFacts }) });
179
180
  files.push(pointer);
180
- topology.routes.push(Object.freeze({ pointerPath, workspaceId: owningWorkspace.workspaceId, workspaceRelativeHandoffPath: String(route.workspaceRelativePath || ''), routeId: String(route.id || ''), sha256: String(route.sha256 || '') }));
181
+ topology.routes.push(Object.freeze({ pointerPath, workspaceId: owningWorkspace.workspaceId, workspaceRelativeHandoffPath: String(route.workspaceRelativePath || ''), routeId: String(route.id || ''), sha256: String(route.sha256 || ''), returnCarrierReservation: route.returnCarrierReservation || null }));
181
182
  }
182
183
 
183
184
  const readFacts = { format: RECIPIENT_V2_PACKAGE_V1_FORMAT_ID, packageRootPath: RECIPIENT_V2_PACKAGE_V1_ROOT_PATH, entryArtifactPath: RECIPIENT_V2_READ_PATH, artifactSurface: 'tiinex.handoff.package.v1-plus-qualified-carried-material', routeAuthority: 'qualified-handoff-route-pointer-plus-exact-qualified-carried-handoff-bytes', routeSelectionAuthority: RECIPIENT_V2_ROUTE_SELECTION_AUTHORITY, siblingRouteInference: RECIPIENT_V2_SIBLING_ROUTE_INFERENCE, carrierLineage: carrier.lineage || null, pathParentProjection: true, pathAuthority: false };
@@ -71,7 +71,13 @@ export function deriveVisibleFacts({ file = null, markdown = '', schemaId = '',
71
71
  const closure = direct || generic;
72
72
  const handoffEntry = (closure?.parsed?.entries || []).find((entry) => entry.path === visible.handoffWorkspacePath) || null;
73
73
  const current = sectionText(markdown, 'Current Read');
74
- return { ...base, workspaceId, workspaceArtifactPath: closure?.workspaceFile?.path || '', workspaceArtifactSha256: closure?.workspaceFile ? sha256Hex(packageFileBytes(closure.workspaceFile)) : '', archivePath: closure?.archiveFile?.path || '', archiveSha256: closure?.archiveFile ? sha256Hex(packageFileBytes(closure.archiveFile)) : '', sourceWorkspaceTargetInnerPath: closure?.workspaceArtifactInnerPath || '', sourceWorkspaceTargetSha256: closure?.sourceWorkspaceTargetSha256 || '', workspaceRelativeHandoffPath: visible.handoffWorkspacePath, handoffBytes: Number(handoffEntry?.bytes || 0), handoffSha256: String(handoffEntry?.sha256 || ''), routeId: visible.routeId || unquote(field(current, 'Route Id')), cacheArtifactPath: markdownTarget(field(current, 'Workspace Dependency Cache')), requiredContextBindings: Object.freeze([]) };
74
+ const returnCarrierReservation = visible.returnPackageCarrierKind
75
+ ? Object.freeze({
76
+ carrierKind: String(visible.returnPackageCarrierKind || ''),
77
+ ...(String(visible.returnPackageCarrierKind || '') === 'non-major' ? { siblingIndex: Number(visible.returnPackageSiblingIndex || 0) } : {})
78
+ })
79
+ : null;
80
+ return { ...base, workspaceId, workspaceArtifactPath: closure?.workspaceFile?.path || '', workspaceArtifactSha256: closure?.workspaceFile ? sha256Hex(packageFileBytes(closure.workspaceFile)) : '', archivePath: closure?.archiveFile?.path || '', archiveSha256: closure?.archiveFile ? sha256Hex(packageFileBytes(closure.archiveFile)) : '', sourceWorkspaceTargetInnerPath: closure?.workspaceArtifactInnerPath || '', sourceWorkspaceTargetSha256: closure?.sourceWorkspaceTargetSha256 || '', workspaceRelativeHandoffPath: visible.handoffWorkspacePath, handoffBytes: Number(handoffEntry?.bytes || 0), handoffSha256: String(handoffEntry?.sha256 || ''), routeId: visible.routeId || unquote(field(current, 'Route Id')), cacheArtifactPath: markdownTarget(field(current, 'Workspace Dependency Cache')), returnCarrierReservation, requiredContextBindings: Object.freeze([]) };
75
81
  }
76
82
  if (role === 'endpoint-role' || role === 'participant-role') {
77
83
  const targetPayload = visible.targetPayload || '';
@@ -16,6 +16,7 @@ import { projectRecipientV2EndpointRoles, projectRecipientV2ParticipantRoles, pr
16
16
  import { RECIPIENT_V2_PACKAGE_V1_FORMAT_ID, RECIPIENT_V2_PACKAGE_V1_ROOT_PATH, RECIPIENT_V2_PACKAGE_V1_SCHEMA_ID } from './recipientV2.packageV1.constants.js';
17
17
  import { BOOTSTRAP_PACKAGE_ROLE, HANDOFF_PACKAGE_ROLE, parseHandoffPackageV1, validatePackageFields, WORKSPACE_PACKAGE_ROLE } from './recipientV2.packageV1.contract.js';
18
18
  import { deriveVisibleFacts, validateRouteClosure } from './recipientV2.packageV1.inspect.helpers.js';
19
+ import { qualifyDelegationReturnReservation } from './delegationReturnReservation.js';
19
20
  import { bootstrapCarrierProjection, workspaceCarrierProjection } from './recipientV2.packageV1.workspaceProjection.js';
20
21
  import { inspectRecipientV2WorkspaceSurface } from './recipientV2.inspect.workspaces.js';
21
22
  import { byteEqual, currentSchemaId, decodeUtf8, dedupeFindings, deepFreeze, oneFile } from './recipientV2.packageV1.shared.js';
@@ -195,7 +196,19 @@ export function inspectRecipientFacingV2PackageV1(bundle = {}, options = {}) {
195
196
  } else {
196
197
  inspectEndpointRolePointers(endpointRolePointers, workspaceParts, caches, findings);
197
198
  inspectParticipantRolePointers(participantRolePointers, workspaceParts, caches, findings);
198
- const routeSpecs = routePointers.map((pointer) => ({ workspaceId: String(pointer.facts?.workspaceId || ''), path: String(pointer.facts?.workspaceRelativeHandoffPath || ''), purpose: '' }));
199
+ const routeSpecs = routePointers.map((pointer) => {
200
+ const workspaceId = String(pointer.facts?.workspaceId || '');
201
+ const path = String(pointer.facts?.workspaceRelativeHandoffPath || '');
202
+ let returnCarrierReservation = pointer.facts?.returnCarrierReservation || null;
203
+ if (!returnCarrierReservation && workspaceId && path) {
204
+ const entry = resolveHandoffWorkspaceEntry(workspaceByteProvider, workspaceId, path);
205
+ if (entry.state === 'qualified') {
206
+ const preflight = qualifyDelegationReturnReservation({ markdown: decodeUtf8(entry.data) });
207
+ if (preflight.state === 'qualified' && preflight.returnExpected) returnCarrierReservation = Object.freeze({ carrierKind: preflight.carrierKind, siblingIndex: preflight.siblingIndex });
208
+ }
209
+ }
210
+ return { workspaceId, path, purpose: '', returnCarrierReservation };
211
+ });
199
212
  carrierProjection = buildHandoffCarrierProjection({ bundle: semanticBundle, descriptor, workspaceByteProvider, carrierLineage: lineage, routes: routeSpecs });
200
213
  if (carrierProjection.status !== 'ready') findings.push(finding('error', 'portable.handoff-package-v1.routes-unqualified', 'Selected Handoff Pointer does not independently resolve to qualified authoritative Handoff bytes.', { causes: carrierProjection.findings || [] }));
201
214
  inspectRoutePointers(routePointers, carrierProjection, workspaceParts, endpointRolePointers, participantRolePointers, index, findings);
@@ -244,7 +257,7 @@ export function inspectRecipientFacingV2PackageV1(bundle = {}, options = {}) {
244
257
  schema: 'tiinex.portable.recipient-facing-handoff-package-v1.inspection.v1', detected: Boolean(packageFile), status, format: RECIPIENT_V2_PACKAGE_V1_FORMAT_ID,
245
258
  rootArtifact: packageFile ? Object.freeze({ path: packageFile.path, schemaId: RECIPIENT_V2_PACKAGE_V1_SCHEMA_ID, sha256: sha256Hex(packageFileBytes(packageFile)), carrierLineage: lineage }) : null,
246
259
  readArtifact, workspaces: Object.freeze(workspaceParts.map((item) => Object.freeze({ workspaceId: item.workspaceId, coverage: String(item.representation?.coverage || item.facts?.coverage || 'complete'), bindingState: item.bindingState || String(item.representation?.bindingState || 'verified'), workspaceArtifactPath: item.artifact.path, workspaceArchivePath: item.archiveFile?.path || '', sourceWorkspaceTargetInnerPath: item.facts.sourceWorkspaceTargetInnerPath, sourceWorkspaceTargetSha256: item.facts.sourceWorkspaceTargetSha256 }))), sealedWorkspaces: Object.freeze(sealedWorkspaceBindings),
247
- routes: projectRecipientV2Routes(routePointers, endpointRolePointers, participantRolePointers), endpointRoles: projectRecipientV2EndpointRoles(endpointRolePointers), participantRoles: projectRecipientV2ParticipantRoles(participantRolePointers),
260
+ routes: projectRecipientV2Routes(routePointers, endpointRolePointers, participantRolePointers, carrierProjection?.routes || []), endpointRoles: projectRecipientV2EndpointRoles(endpointRolePointers), participantRoles: projectRecipientV2ParticipantRoles(participantRolePointers),
248
261
  caches: Object.freeze(caches.map((cache) => Object.freeze({ workspaceId: String(cache.facts?.workspaceId || ''), artifactPath: cache.artifact.path, archivePath: cache.file.path, materials: cache.facts.materials || [] }))),
249
262
  bootstrapInspection, transportManifest: null, artifactFacts: Object.freeze(generatedArtifacts.map((item) => Object.freeze({ path: item.path, facts: item.facts }))), descriptor, workspaceByteProvider, carrierProjection, coldConsumerProjection,
250
263
  packageContract, findings: Object.freeze(finalFindings), findingSummary: Object.freeze({ errors: finalFindings.filter((item) => item.severity === 'error').length, findings: finalFindings.length }),
@@ -6,6 +6,8 @@ export function parseRecipientV2Pointer(markdown = '') {
6
6
  workspaceId: unquoteCode(fieldValue(current, 'Workspace Id')),
7
7
  workspacePayload: markdownTarget(fieldValue(current, 'Workspace Payload')),
8
8
  handoffWorkspacePath: unquoteCode(fieldValue(current, 'Handoff Workspace Path')),
9
+ returnPackageCarrierKind: unquoteCode(fieldValue(current, 'Return Package Carrier Kind')),
10
+ returnPackageSiblingIndex: unquoteCode(fieldValue(current, 'Return Package Sibling Index')),
9
11
  routeId: unquoteCode(fieldValue(current, 'Route Id')),
10
12
  routeSelection: fieldValue(current, 'Route Selection'),
11
13
  selectedRouteId: unquoteCode(fieldValue(current, 'Selected Route Id')),
@@ -0,0 +1,145 @@
1
+ import { comparePortableSourceFrontiers, createPortableSourceFrontier } from '../comparison/sourceFrontierComparison.js';
2
+ import { inspectRecipientFacingV2Topology } from './recipientV2.inspect.js';
3
+ import { handoffWorkspaceProviderForId, listHandoffWorkspaceEntries } from './workspaceByteProvider.js';
4
+
5
+ export const PORTABLE_RECOVERY_ACCEPTANCE_AUDIT_SCHEMA_ID = 'tiinex.portable.recovery-acceptance-audit.v1';
6
+
7
+ export function auditPortableRecoveryAcceptance(input = {}) {
8
+ const basisInspection = inspectRecipientFacingV2Topology(input.basis?.bundle || input.basis || {});
9
+ const candidateInspection = inspectRecipientFacingV2Topology(input.candidate?.bundle || input.candidate || {});
10
+ const findings = [];
11
+ if (String(basisInspection.status || '') !== 'valid') findings.push(finding('error', 'portable.recovery-acceptance.basis-unqualified', 'Recovery acceptance audit requires one independently qualified accepted-basis Handoff package.'));
12
+ if (String(candidateInspection.status || '') !== 'valid') findings.push(finding('error', 'portable.recovery-acceptance.candidate-unqualified', 'Recovery acceptance audit requires one independently qualified candidate Handoff package.'));
13
+ if (findings.some((item) => item.severity === 'error')) return auditResult('blocked', [], findings, basisInspection, candidateInspection, input);
14
+
15
+ const candidateIds = [...new Set((candidateInspection.workspaces || []).map((item) => normalizeId(item.workspaceId)).filter(Boolean))].sort();
16
+ const requestedIds = [...new Set([...(input.workspaceIds || input.selectedWorkspaceIds || [])].map(normalizeId).filter(Boolean))].sort();
17
+ const ids = requestedIds.length ? requestedIds : candidateIds;
18
+ const expectedRemovals = normalizeExpectedRemovals(input.expectedRemovals || {});
19
+ const workspaces = ids.map((workspaceId) => auditWorkspace({ workspaceId, basisInspection, candidateInspection, expectedRemovals, findings }));
20
+ for (const workspaceId of requestedIds) if (!candidateIds.includes(workspaceId)) findings.push(finding('error', 'portable.recovery-acceptance.workspace-unresolved', 'Explicitly requested candidate Workspace is not present in the qualified candidate carrier.', { workspaceId }));
21
+ const status = findings.some((item) => item.severity === 'error') || workspaces.some((item) => item.state !== 'ready') ? 'blocked' : 'ready';
22
+ return auditResult(status, workspaces, findings, basisInspection, candidateInspection, input);
23
+ }
24
+
25
+ function auditWorkspace({ workspaceId, basisInspection, candidateInspection, expectedRemovals, findings }) {
26
+ const basisProvider = handoffWorkspaceProviderForId(basisInspection.workspaceByteProvider, workspaceId);
27
+ const candidateProvider = handoffWorkspaceProviderForId(candidateInspection.workspaceByteProvider, workspaceId);
28
+ const candidateTopology = (candidateInspection.workspaces || []).find((item) => normalizeId(item.workspaceId) === workspaceId) || null;
29
+ const basisTopology = (basisInspection.workspaces || []).find((item) => normalizeId(item.workspaceId) === workspaceId) || null;
30
+ const reasons = [];
31
+ if (basisProvider.state !== 'qualified') {
32
+ reasons.push('basis-workspace-unqualified');
33
+ findings.push(finding('error', 'portable.recovery-acceptance.basis-workspace-unqualified', 'Accepted-basis Workspace bytes are unavailable or unqualified.', { workspaceId, state: String(basisProvider.state || '') }));
34
+ }
35
+ if (candidateProvider.state !== 'qualified') {
36
+ reasons.push('candidate-workspace-unqualified');
37
+ findings.push(finding('error', 'portable.recovery-acceptance.candidate-workspace-unqualified', 'Candidate Workspace bytes are unavailable or unqualified.', { workspaceId, state: String(candidateProvider.state || '') }));
38
+ }
39
+ const coverage = String(candidateTopology?.coverage || '');
40
+ if (coverage !== 'complete') {
41
+ reasons.push('candidate-workspace-not-complete');
42
+ findings.push(finding('error', 'portable.recovery-acceptance.candidate-workspace-not-complete', 'Master Recovery acceptance requires a complete candidate Workspace representation; bounded coverage cannot prove restart suitability.', { workspaceId, coverage }));
43
+ }
44
+ if (reasons.length) return freeze({ workspaceId, state: 'blocked', coverage, reasons: freeze(reasons), comparisonState: 'unavailable', counts: emptyCounts(), unexplainedRemovals: freeze([]), expectedRemovals: freeze([...expectedRemovals.get(workspaceId) || []]), materialization: materializationReceipt(candidateProvider, candidateTopology) });
45
+
46
+ const basis = frontierForProvider(workspaceId, basisProvider, 'accepted-basis');
47
+ const candidate = frontierForProvider(workspaceId, candidateProvider, 'candidate-recovery');
48
+ const comparison = comparePortableSourceFrontiers({ left: basis, right: candidate });
49
+ const workspace = (comparison.workspaces || []).find((item) => normalizeId(item.workspaceId) === workspaceId) || null;
50
+ if (comparison.status !== 'ready' || !workspace || !workspace.delta) {
51
+ reasons.push('workspace-comparison-unqualified');
52
+ findings.push(finding('error', 'portable.recovery-acceptance.workspace-comparison-unqualified', 'Accepted-basis to candidate Workspace comparison did not qualify.', { workspaceId, state: String(workspace?.state || comparison.state || '') }));
53
+ return freeze({ workspaceId, state: 'blocked', coverage, reasons: freeze(reasons), comparisonState: String(workspace?.state || comparison.state || ''), counts: emptyCounts(), unexplainedRemovals: freeze([]), expectedRemovals: freeze([...expectedRemovals.get(workspaceId) || []]), materialization: materializationReceipt(candidateProvider, candidateTopology) });
54
+ }
55
+
56
+ const removals = (workspace.delta.removed || []).map((entry) => String(entry.path || '')).filter(Boolean);
57
+ const expected = expectedRemovals.get(workspaceId) || new Set();
58
+ const unexplained = removals.filter((path) => !expected.has(path));
59
+ const staleExpected = [...expected].filter((path) => !removals.includes(path));
60
+ if (unexplained.length) {
61
+ reasons.push('unexplained-removals');
62
+ findings.push(finding('error', 'portable.recovery-acceptance.unexplained-removals', 'Candidate Recovery removes accepted-basis source paths without an explicit expected-removal disposition.', { workspaceId, count: unexplained.length, paths: unexplained }));
63
+ }
64
+ if (staleExpected.length) findings.push(finding('warning', 'portable.recovery-acceptance.expected-removal-stale', 'One or more declared expected removals are not removals in the candidate carrier.', { workspaceId, count: staleExpected.length, paths: staleExpected }));
65
+ return freeze({
66
+ workspaceId,
67
+ state: reasons.length ? 'blocked' : 'ready',
68
+ coverage,
69
+ reasons: freeze(reasons),
70
+ basisCoverage: String(basisTopology?.coverage || ''),
71
+ comparisonState: String(workspace.state || ''),
72
+ counts: freeze({
73
+ additions: Number(workspace.delta.counts?.added || 0),
74
+ removals: Number(workspace.delta.counts?.removed || 0),
75
+ byteChanged: Number(workspace.delta.counts?.byteChanged || 0),
76
+ unexplainedRemovals: unexplained.length,
77
+ totalChanges: Number(workspace.delta.counts?.total || 0)
78
+ }),
79
+ unexplainedRemovals: freeze(unexplained),
80
+ expectedRemovals: freeze([...expected]),
81
+ staleExpectedRemovals: freeze(staleExpected),
82
+ materialization: materializationReceipt(candidateProvider, candidateTopology)
83
+ });
84
+ }
85
+
86
+ function auditResult(status, workspaces, findings, basisInspection, candidateInspection, input) {
87
+ const unexplainedRemovalCount = workspaces.reduce((sum, item) => sum + Number(item.counts?.unexplainedRemovals || 0), 0);
88
+ const completeWorkspaceCount = workspaces.filter((item) => item.coverage === 'complete').length;
89
+ const allReady = workspaces.length > 0 && workspaces.every((item) => item.state === 'ready');
90
+ return freeze({
91
+ schema: PORTABLE_RECOVERY_ACCEPTANCE_AUDIT_SCHEMA_ID,
92
+ status,
93
+ state: status === 'ready' && allReady ? 'acceptance-audit-ready' : 'acceptance-audit-blocked',
94
+ basisQualification: String(basisInspection.status || 'invalid'),
95
+ candidateQualification: String(candidateInspection.status || 'invalid'),
96
+ selectionMode: (input.workspaceIds || input.selectedWorkspaceIds || []).length ? 'explicit-workspace-set' : 'all-candidate-workspaces',
97
+ workspaces: freeze(workspaces),
98
+ counts: freeze({ workspaces: workspaces.length, readyWorkspaces: workspaces.filter((item) => item.state === 'ready').length, completeWorkspaces: completeWorkspaceCount, unexplainedRemovals: unexplainedRemovalCount }),
99
+ suitability: freeze({
100
+ state: status === 'ready' && allReady && unexplainedRemovalCount === 0 ? 'restart-source-ready' : 'blocked',
101
+ candidateCarrierQualified: String(candidateInspection.status || '') === 'valid',
102
+ exactWorkspaceMaterializationQualified: workspaces.length > 0 && workspaces.every((item) => item.materialization?.state === 'qualified'),
103
+ allSelectedWorkspacesComplete: workspaces.length > 0 && completeWorkspaceCount === workspaces.length,
104
+ unexplainedRemovalCount,
105
+ gitCommitStateProven: false,
106
+ semanticAcceptanceGranted: false
107
+ }),
108
+ findings: freeze(findings),
109
+ boundary: 'Coarse Recovery acceptance audit over already-qualified carrier bytes. It decodes the exact candidate Workspace representations, compares them to one explicit accepted basis, and fails on unexplained source removals or incomplete coverage. It does not inspect a live checkout, prove Git cleanliness/committability, decide semantic correctness, authorize deletion, or grant Master Recovery acceptance; target landing still requires the separate exact source preflight.'
110
+ });
111
+ }
112
+
113
+ function frontierForProvider(workspaceId, provider, label) {
114
+ const entries = listHandoffWorkspaceEntries({ workspaces: [provider] }, workspaceId);
115
+ return createPortableSourceFrontier({ id: `${label}:${workspaceId}`, source: { kind: label, workspaceId }, workspaces: [{ workspaceId, entries: entries.map(entryIdentity) }] });
116
+ }
117
+ function materializationReceipt(provider, topology) {
118
+ return freeze({ state: provider?.state === 'qualified' ? 'qualified' : 'unqualified', mode: String(provider?.mode || ''), coverage: String(topology?.coverage || ''), entryCount: Array.isArray(provider?.entries) ? provider.entries.length : 0, archivePackagePath: String(provider?.archive?.packagePath || '') });
119
+ }
120
+ function normalizeExpectedRemovals(value) {
121
+ const source = value?.expectedRemovals && typeof value.expectedRemovals === 'object' ? value.expectedRemovals : value;
122
+ const out = new Map();
123
+ if (Array.isArray(source)) {
124
+ for (const item of source) {
125
+ const workspaceId = normalizeId(item?.workspaceId || item?.workspace || '');
126
+ const path = normalizePath(item?.path || '');
127
+ if (!workspaceId || !path) continue;
128
+ if (!out.has(workspaceId)) out.set(workspaceId, new Set());
129
+ out.get(workspaceId).add(path);
130
+ }
131
+ return out;
132
+ }
133
+ for (const [key, paths] of Object.entries(source || {})) {
134
+ const workspaceId = normalizeId(key);
135
+ if (!workspaceId) continue;
136
+ out.set(workspaceId, new Set((Array.isArray(paths) ? paths : [paths]).map(normalizePath).filter(Boolean)));
137
+ }
138
+ return out;
139
+ }
140
+ function normalizeId(value = '') { return String(value || '').trim().toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, ''); }
141
+ function normalizePath(value = '') { return String(value || '').replace(/\\/g, '/').replace(/^\.\//, '').trim(); }
142
+ function entryIdentity(entry = {}) { return freeze({ path: String(entry.path || entry.innerPath || ''), bytes: Number(entry.bytes ?? entry.size ?? 0), sha256: String(entry.sha256 || '').toLowerCase() }); }
143
+ function emptyCounts() { return freeze({ additions: 0, removals: 0, byteChanged: 0, unexplainedRemovals: 0, totalChanges: 0 }); }
144
+ function finding(severity, code, message, context = {}) { return freeze({ severity, code, message, context: freeze({ ...context }) }); }
145
+ function freeze(value) { if (Array.isArray(value)) return Object.freeze(value.map((item) => freeze(item))); if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value; return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, freeze(item)]))); }