@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.
- package/package.json +7 -6
- package/src/integrity/integrity.c14nV1.js +28 -0
- package/src/lineage/lineage.integrity.js +108 -22
- package/src/lineage/lineage.resolve.js +21 -14
- package/src/lineage/lineage.sourceScope.js +41 -12
- package/src/lineage/lineage.targetKeys.js +26 -7
- package/src/tooling/portable/adapters/cli/cli.command-input.js +11 -0
- package/src/tooling/portable/adapters/cli/cli.ground-materialize.js +2 -0
- package/src/tooling/portable/adapters/cli/cli.handoff-manufacture.js +4 -1
- package/src/tooling/portable/adapters/cli/cli.help.js +1 -1
- package/src/tooling/portable/adapters/cli/cli.land.js +23 -1
- package/src/tooling/portable/adapters/cli/cli.run.js +1 -1
- package/src/tooling/portable/adapters/node/handoff.manufacture.js +9 -1
- package/src/tooling/portable/handoff/carrierProjection.js +2 -2
- package/src/tooling/portable/handoff/carrierProjection.routeQualification.js +1 -0
- package/src/tooling/portable/handoff/delegationReturnReservation.js +101 -0
- package/src/tooling/portable/handoff/manufacture.js +7 -1
- package/src/tooling/portable/handoff/materialClosure.archiveV2.js +1 -1
- package/src/tooling/portable/handoff/recipientV2.artifactInspection.js +11 -0
- package/src/tooling/portable/handoff/recipientV2.inspect.projection.js +7 -3
- package/src/tooling/portable/handoff/recipientV2.packageV1.build.js +3 -2
- package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.helpers.js +7 -1
- package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.js +15 -2
- package/src/tooling/portable/handoff/recipientV2.pointer.js +2 -0
- package/src/tooling/portable/handoff/recoveryAcceptanceAudit.js +145 -0
- package/src/tooling/portable/handoff/workspaceLandingPlan.js +110 -13
- 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
|
|
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) =>
|
|
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
|
-
|
|
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) =>
|
|
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)]))); }
|