@tiinex/core 0.15.0 → 0.17.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 (22) hide show
  1. package/package.json +7 -6
  2. package/src/tooling/portable/adapters/cli/cli.command-input.js +11 -0
  3. package/src/tooling/portable/adapters/cli/cli.ground-materialize.js +2 -0
  4. package/src/tooling/portable/adapters/cli/cli.handoff-manufacture.js +4 -1
  5. package/src/tooling/portable/adapters/cli/cli.help.js +1 -1
  6. package/src/tooling/portable/adapters/cli/cli.land.js +23 -1
  7. package/src/tooling/portable/adapters/cli/cli.run.js +1 -1
  8. package/src/tooling/portable/adapters/node/handoff.manufacture.js +9 -1
  9. package/src/tooling/portable/handoff/carrierProjection.js +2 -2
  10. package/src/tooling/portable/handoff/carrierProjection.routeQualification.js +1 -0
  11. package/src/tooling/portable/handoff/delegationReturnReservation.js +101 -0
  12. package/src/tooling/portable/handoff/manufacture.js +7 -1
  13. package/src/tooling/portable/handoff/materialClosure.archiveV2.js +1 -1
  14. package/src/tooling/portable/handoff/recipientV2.artifactInspection.js +11 -0
  15. package/src/tooling/portable/handoff/recipientV2.inspect.projection.js +7 -3
  16. package/src/tooling/portable/handoff/recipientV2.packageV1.build.js +3 -2
  17. package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.helpers.js +7 -1
  18. package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.js +15 -2
  19. package/src/tooling/portable/handoff/recipientV2.pointer.js +2 -0
  20. package/src/tooling/portable/handoff/recoveryAcceptanceAudit.js +145 -0
  21. package/src/tooling/portable/handoff/workspaceLandingPlan.js +110 -13
  22. package/src/tooling/portable/operation.catalog.package.js +9 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiinex/core",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "Shared host-neutral Tiinex implementation core for artifacts, schemas, validation, lineage, grounding, Handoffs, provenance and deterministic workflows.",
5
5
  "type": "module",
6
6
  "sideEffects": true,
@@ -136,7 +136,8 @@
136
136
  "./node/release-policy": "./src/release/policy.mjs",
137
137
  "./tooling/portable/source/sourceEligibility.js": "./src/tooling/portable/source/sourceEligibility.js",
138
138
  "./tooling/portable/comparison/sourceFrontierComparison.js": "./src/tooling/portable/comparison/sourceFrontierComparison.js",
139
- "./tooling/portable/adapters/node/sourceFrontierComparison.js": "./src/tooling/portable/adapters/node/sourceFrontierComparison.js"
139
+ "./tooling/portable/adapters/node/sourceFrontierComparison.js": "./src/tooling/portable/adapters/node/sourceFrontierComparison.js",
140
+ "./tooling/portable/handoff/recoveryAcceptanceAudit.js": "./src/tooling/portable/handoff/recoveryAcceptanceAudit.js"
140
141
  },
141
142
  "files": [
142
143
  "src",
@@ -168,12 +169,12 @@
168
169
  "type": "git",
169
170
  "url": "git+https://github.com/Tiinex/core.git"
170
171
  },
171
- "gitHead": "d257bb3ae9e7c6361840d727ce5e359d1b6f6091",
172
+ "gitHead": "2986187f3e95582d13c784060272e111931c6a1b",
172
173
  "tiinexRelease": {
173
174
  "policy": "tiinex.master-npm-release.v1",
174
- "sourceCommit": "d257bb3ae9e7c6361840d727ce5e359d1b6f6091",
175
- "sourceTree": "002c94ff43122ac8188de9514909dfdabd348803",
175
+ "sourceCommit": "2986187f3e95582d13c784060272e111931c6a1b",
176
+ "sourceTree": "0c65ff04241c756ae67978d777d1bee8ac1befc9",
176
177
  "repository": "Tiinex/core",
177
- "previousVersion": "0.14.0"
178
+ "previousVersion": "0.16.0"
178
179
  }
179
180
  }
@@ -141,6 +141,17 @@ export async function commandInput(parsed, runtime = {}) {
141
141
  }
142
142
 
143
143
  if (parsed.command === 'compare-source-frontiers') return prepareSourceFrontierComparisonCliInput(parsed, flags);
144
+ if (parsed.command === 'audit-recovery-acceptance') {
145
+ const basisPath = String(flags.basis || parsed.positionals[0] || '').trim();
146
+ const candidatePath = String(flags.candidate || parsed.positionals[1] || '').trim();
147
+ if (!basisPath || !candidatePath) throw new Error('portable.cli.recovery-acceptance.basis-candidate-required');
148
+ const [basis, candidate, expectedValue] = await Promise.all([
149
+ loadNodePortableInput([basisPath], { maxFiles: flags['max-carrier-files'] || 10000, maxTextBytes: flags['max-text-bytes'] || 16 * 1024 * 1024 }),
150
+ loadNodePortableInput([candidatePath], { maxFiles: flags['max-carrier-files'] || 10000, maxTextBytes: flags['max-text-bytes'] || 16 * 1024 * 1024 }),
151
+ readOptionalJson(flags['expected-removals'])
152
+ ]);
153
+ return { input: { basis, candidate, workspaceIds: splitFlag(flags.workspaces || flags['workspace-ids']), expectedRemovals: expectedValue.expectedRemovals || expectedValue || {} }, options: {} };
154
+ }
144
155
  if (parsed.command === 'prove-source-reconciliation') {
145
156
  const dispositions = await readOptionalJson(flags.dispositions || flags['disposition-file']);
146
157
  return prepareSourceFrontierReconciliationCliInput(flags, dispositions);
@@ -50,6 +50,8 @@ export async function materializeGroundWorkspaceCliOutput(result = {}, input = {
50
50
  workspaceTarget: String(workspaceInspection.sourceWorkspaceTargetInnerPath || ''),
51
51
  roleLabel: String(result?.authority?.role?.label || ''),
52
52
  returnOutputDir: path.dirname(path.resolve(String(input.packageSourcePath || outputDir))),
53
+ returnPackageCarrierKind: String((inspection.routes || []).find((item) => String(item.pointerPath || '') === String(result?.authority?.route?.pointerPath || input.route || ''))?.returnCarrierReservation?.carrierKind || ''),
54
+ returnPackageSiblingIndex: String((inspection.routes || []).find((item) => String(item.pointerPath || '') === String(result?.authority?.route?.pointerPath || input.route || ''))?.returnCarrierReservation?.siblingIndex || ''),
53
55
  boundary: 'Runtime-only continuation state carried forward from one qualified ground --continue receipt. It is excluded from canonical Workspace manufacture and is not semantic authority.'
54
56
  });
55
57
  const continuationStatePath = path.join(outputDir, '.tiinex', 'continuation.json');
@@ -26,6 +26,7 @@ export async function prepareHandoffManufactureCliCommand(parsed = {}, runtime =
26
26
  const handoffPath = flags.handoff || parsed.positionals?.[1] || continuationState.returnHandoffPath || '';
27
27
  if (!flags['workspace-id'] && continuationState.workspaceId) flags['workspace-id'] = continuationState.workspaceId;
28
28
  if (!flags['workspace-target'] && continuationState.workspaceTarget) flags['workspace-target'] = continuationState.workspaceTarget;
29
+ if (!flags['package-sibling-index'] && continuationState.returnPackageSiblingIndex) flags['package-sibling-index'] = continuationState.returnPackageSiblingIndex;
29
30
  if (!flags['package-parent'] && continuationState.packageParentPath) flags['package-parent'] = continuationState.packageParentPath;
30
31
  if (!flags.route && handoffPath) flags.route = handoffPath;
31
32
  if (!flags.output && !flags['output-dir'] && parsed.surfaceCommand === 'handoff' && continuationState.returnOutputDir) flags['output-dir'] = continuationState.returnOutputDir;
@@ -135,7 +136,9 @@ export async function prepareHandoffManufactureCliCommand(parsed = {}, runtime =
135
136
  packageParentWorkspaceIds,
136
137
  packageParentWorkspaceAliases,
137
138
  reconciliationProof,
138
- requireReconciliationProof
139
+ requireReconciliationProof,
140
+ returnPackageSiblingIndex: flags['return-package-sibling-index'],
141
+ returnPackageMajor: Boolean(flags['return-package-major'])
139
142
  }, runtime);
140
143
  return {
141
144
  input,
@@ -83,7 +83,7 @@ function commonCommandHelp(command, surfaceCommand) {
83
83
  '',
84
84
  `${command} handoff <workspace-dir>`,
85
85
  '',
86
- 'Infers the latest qualified authored Handoff, selected Workspace identity/target, received package parent as carrier-lineage evidence, canonical projected filename, and return output directory. A non-major continuation that writes a carrier must supply an explicitly coordinated `--package-sibling-index <1..9999>`; Tooling will not silently discover the next sibling because isolated runtimes cannot prove a globally unique reservation. Distinct explicit sibling indexes preserve same-Major parallelism, while byte-identical duplicate transport at one exact output path is idempotent and divergent bytes fail closed. It does not implicitly carry sibling Workspaces from the received package; advanced manufacture must select exact reusable parent snapshots with `--package-parent-workspaces <id,...|all>`. Recovery/integration manufacture can bind the reconciled byte proof with `--require-reconciliation-proof --reconciliation-proof <full-reconcile-receipt.json>`; missing, non-ready, edited, or source-stale proof blocks package output. The default receipt keeps output identity, routing text, closure/workspace qualification, verification, and actionable findings compact; add `--full` for the complete manufacture receipt. Normal operator completion is exactly one Handoff package plus the adjacent exact routing text. In markdown-capable hosts render that routing in a fenced code block; do not emit canonical Workspace Evidence/Handoff markdown as additional loose transport files. Runtime-only `.tiinex` state is excluded from canonical manufacture.',
86
+ 'Infers the latest qualified authored Handoff, selected Workspace identity/target, received package parent as carrier-lineage evidence, canonical projected filename, and return output directory. When the outgoing Handoff declares `Signal Kind: return`, delegation manufacture is transport-not-ready unless the delegator supplies `--return-package-sibling-index <1..9999>` for the expected non-Major return, or explicitly declares `--return-package-major`; the qualified reservation is carried in the route pointer and later continuation state. A non-major continuation that writes a carrier must supply an explicitly coordinated `--package-sibling-index <1..9999>`; Tooling will not silently discover the next sibling because isolated runtimes cannot prove a globally unique reservation. Distinct explicit sibling indexes preserve same-Major parallelism, while byte-identical duplicate transport at one exact output path is idempotent and divergent bytes fail closed. It does not implicitly carry sibling Workspaces from the received package; advanced manufacture must select exact reusable parent snapshots with `--package-parent-workspaces <id,...|all>`. Recovery/integration manufacture can bind the reconciled byte proof with `--require-reconciliation-proof --reconciliation-proof <full-reconcile-receipt.json>`; missing, non-ready, edited, or source-stale proof blocks package output. The default receipt keeps output identity, routing text, closure/workspace qualification, verification, and actionable findings compact; add `--full` for the complete manufacture receipt. Normal operator completion is exactly one Handoff package plus the adjacent exact routing text. In markdown-capable hosts render that routing in a fenced code block; do not emit canonical Workspace Evidence/Handoff markdown as additional loose transport files. Runtime-only `.tiinex` state is excluded from canonical manufacture.',
87
87
  '',
88
88
  `Advanced/internal catalog: ${command} operations`
89
89
  ];
@@ -1,13 +1,35 @@
1
+ import { prepareNodeSourceFrontier } from '../node/sourceFrontierComparison.js';
2
+
1
3
  export async function land(flags, material, readOptionalJson, splitFlag) {
2
4
  const repositoriesValue = await readOptionalJson(flags.repositories || flags.repos);
3
5
  const selectionsValue = await readOptionalJson(flags.selections);
6
+ const repositories = repositoriesValue.repositories || (Array.isArray(repositoriesValue) ? repositoriesValue : []);
4
7
  return {
5
8
  input: {
6
9
  ...material,
7
- repositories: repositoriesValue.repositories || (Array.isArray(repositoriesValue) ? repositoriesValue : []),
10
+ repositories: await attachExactTargetSnapshots(repositories, flags),
8
11
  selections: selectionsValue.selections || selectionsValue || repositoriesValue.selections || {},
9
12
  workspaceIds: splitFlag(flags.workspaces || flags['workspace-ids'])
10
13
  },
11
14
  options: {}
12
15
  };
13
16
  }
17
+
18
+ async function attachExactTargetSnapshots(repositories = [], flags = {}) {
19
+ return Promise.all(repositories.map(async (repository, index) => {
20
+ if (repository?.sourceSnapshot || repository?.snapshot || !String(repository?.root || repository?.path || '').trim()) return repository;
21
+ const root = String(repository.root || repository.path || '').trim();
22
+ const workspaceId = String(repository.workspaceId || repository.id || `landing-target-${index + 1}`);
23
+ const frontier = await prepareNodeSourceFrontier({ kind: 'local-workspace', path: root, workspaceId, label: `workspace-landing-target:${workspaceId}` }, { maxFiles: flags['max-files'] });
24
+ const workspace = (frontier.workspaces || [])[0] || null;
25
+ return {
26
+ ...repository,
27
+ sourceSnapshot: workspace?.snapshot || {
28
+ state: 'qualification-error',
29
+ entries: [],
30
+ evidence: {},
31
+ findings: frontier.findings || []
32
+ }
33
+ };
34
+ }));
35
+ }
@@ -425,7 +425,7 @@ function withCliPhaseTiming(result = {}, timing = {}) {
425
425
  function parseArgs(argv=[]) {
426
426
  const args=[...argv],first=args.shift()||'';
427
427
  if(first==='--help'||first==='-h') return {command:'help',flags:{help:true},positionals:[]};
428
- const command=({orient:'orient-handoff-package',ground:'project-grounding-readiness',receive:'qualify-cold-start',validate:'audit-handoff-package-context',handoff:'manufacture-handoff-package',author:'author',compare:'compare-source-frontiers',reconcile:'prove-source-reconciliation'})[first]||first;
428
+ const command=({orient:'orient-handoff-package',ground:'project-grounding-readiness',receive:'qualify-cold-start',validate:'audit-handoff-package-context',handoff:'manufacture-handoff-package',author:'author',compare:'compare-source-frontiers',reconcile:'prove-source-reconciliation','audit-recovery':'audit-recovery-acceptance'})[first]||first;
429
429
  const flags={},positionals=[];
430
430
  while(args.length){const token=args.shift();if(!token.startsWith('--')){positionals.push(token);continue;}const key=token.slice(2);flags[key]=!args.length||args[0].startsWith('--')?true:args.shift();}
431
431
  return {command,flags,positionals,surfaceCommand:first};
@@ -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)]))); }
@@ -1,3 +1,4 @@
1
+ import { createPortableSourceFrontier, reconcilePortableSourceFrontiers } from '../comparison/sourceFrontierComparison.js';
1
2
  import { inspectRecipientFacingV2Topology } from './recipientV2.inspect.js';
2
3
  import { parseWorkspaceEntrypoints, normalizeRepositoryIdentity } from './workspaceSourceIdentity.js';
3
4
 
@@ -42,25 +43,25 @@ function planWorkspace({ workspace = {}, provider = null, repositories = [], sel
42
43
  };
43
44
  if (!provider || provider.state !== 'qualified' || provider.mode !== 'archive') {
44
45
  if (explicit) findings.push(finding('error', 'portable.workspace-landing.workspace-provider-unqualified', 'Requested Workspace lacks a qualified archive byte provider.', { workspaceId }));
45
- return freeze({ ...base, state: 'unavailable', source: null, repository: null, reasons: ['workspace-provider-unqualified'] });
46
+ return freeze({ ...base, state: 'unavailable', source: null, repository: null, preflight: unavailablePreflight('workspace-provider-unqualified'), reasons: ['workspace-provider-unqualified'] });
46
47
  }
47
48
  const target = (provider.entries || []).find((entry) => String(entry.path || '') === base.workspaceArtifactInnerPath);
48
49
  if (!target?.data) {
49
50
  if (explicit) findings.push(finding('error', 'portable.workspace-landing.workspace-target-unavailable', 'Requested Workspace durable source artifact bytes are unavailable.', { workspaceId }));
50
- return freeze({ ...base, state: 'unavailable', source: null, repository: null, reasons: ['workspace-target-unavailable'] });
51
+ return freeze({ ...base, state: 'unavailable', source: null, repository: null, preflight: unavailablePreflight('workspace-target-unavailable'), reasons: ['workspace-target-unavailable'] });
51
52
  }
52
53
  let markdown = '';
53
54
  try { markdown = new TextDecoder('utf-8', { fatal: true }).decode(byteView(target.data)); }
54
55
  catch {
55
56
  findings.push(finding('error', 'portable.workspace-landing.workspace-target-nontext', 'Qualified Workspace target could not be decoded as UTF-8 Markdown.', { workspaceId }));
56
- return freeze({ ...base, state: 'unavailable', source: null, repository: null, reasons: ['workspace-target-nontext'] });
57
+ return freeze({ ...base, state: 'unavailable', source: null, repository: null, preflight: unavailablePreflight('workspace-target-nontext'), reasons: ['workspace-target-nontext'] });
57
58
  }
58
59
  const sources = parseWorkspaceEntrypoints(markdown);
59
60
  const repoSources = sources.filter((source) => normalizeRepositoryIdentity(source.repository));
60
61
  const sourceIdentities = [...new Set(repoSources.map((source) => normalizeRepositoryIdentity(source.repository)))];
61
62
  if (sourceIdentities.length !== 1) {
62
63
  if (explicit) findings.push(finding('error', sourceIdentities.length ? 'portable.workspace-landing.workspace-repository-ambiguous' : 'portable.workspace-landing.workspace-repository-missing', 'Requested Workspace must expose exactly one explicit repository identity before it can target a local Git repository.', { workspaceId, count: sourceIdentities.length }));
63
- return freeze({ ...base, state: 'not-targetable', source: repoSources[0] || null, repository: null, reasons: [sourceIdentities.length ? 'workspace-repository-ambiguous' : 'workspace-repository-missing'] });
64
+ return freeze({ ...base, state: 'not-targetable', source: repoSources[0] || null, repository: null, preflight: unavailablePreflight(sourceIdentities.length ? 'workspace-repository-ambiguous' : 'workspace-repository-missing'), reasons: [sourceIdentities.length ? 'workspace-repository-ambiguous' : 'workspace-repository-missing'] });
64
65
  }
65
66
  const source = repoSources.find((item) => normalizeRepositoryIdentity(item.repository) === sourceIdentities[0]) || repoSources[0];
66
67
  const matches = repositories.filter((repo) => repo.repositoryIdentity === sourceIdentities[0]);
@@ -72,28 +73,112 @@ function planWorkspace({ workspace = {}, provider = null, repositories = [], sel
72
73
  if (!selected) {
73
74
  if (matches.length > 1) findings.push(finding('error', 'portable.workspace-landing.local-repository-ambiguous', 'Multiple local repositories match the same qualified Workspace repository identity; explicit selection is required.', { workspaceId, repository: source.repository, count: matches.length }));
74
75
  else if (explicit || selectionId) findings.push(finding('error', 'portable.workspace-landing.local-repository-unmatched', 'Requested Workspace has no matching local Git repository.', { workspaceId, repository: source.repository }));
75
- return freeze({ ...base, state: matches.length > 1 ? 'ambiguous' : 'unmatched', source, repository: null, candidateRepositoryIds: matches.map((item) => item.id), reasons: [matches.length > 1 ? 'local-repository-ambiguous' : 'local-repository-unmatched'] });
76
+ return freeze({ ...base, state: matches.length > 1 ? 'ambiguous' : 'unmatched', source, repository: null, preflight: unavailablePreflight(matches.length > 1 ? 'local-repository-ambiguous' : 'local-repository-unmatched'), candidateRepositoryIds: matches.map((item) => item.id), reasons: [matches.length > 1 ? 'local-repository-ambiguous' : 'local-repository-unmatched'] });
76
77
  }
77
78
  const reasons = [];
78
- if (selected.clean !== true) {
79
- reasons.push(selected.clean === false ? 'dirty-worktree' : 'clean-state-unresolved');
80
- findings.push(finding('error', selected.clean === false ? 'portable.workspace-landing.local-repository-dirty' : 'portable.workspace-landing.local-repository-clean-state-unresolved', 'Landing requires a clean target worktree except ignored local material.', { workspaceId, repositoryId: selected.id }));
81
- }
82
79
  const declaredRef = String(source.ref || '').trim();
83
80
  const branch = String(selected.branch || '').trim();
84
81
  if (declaredRef && branch && declaredRef !== branch) {
85
82
  reasons.push('ref-branch-mismatch');
86
83
  findings.push(finding('error', 'portable.workspace-landing.ref-branch-mismatch', 'Local repository branch differs from the qualified Workspace source ref safety constraint.', { workspaceId, repositoryId: selected.id, declaredRef, branch }));
87
84
  }
85
+ const preflight = projectExactLandingPreflight(workspaceId, provider, selected, findings);
86
+ if (preflight.state !== 'exact-safe') reasons.push(preflight.state === 'reconciliation-required' ? 'reconciliation-required' : 'target-source-snapshot-unresolved');
88
87
  return freeze({
89
88
  ...base,
90
89
  state: reasons.length ? 'blocked' : 'ready',
91
90
  source,
92
- repository: freeze({ id: selected.id, root: selected.root, repository: selected.repository, repositoryIdentity: selected.repositoryIdentity, branch, clean: selected.clean }),
91
+ repository: freeze({ id: selected.id, root: selected.root, repository: selected.repository, repositoryIdentity: selected.repositoryIdentity, branch, clean: selected.clean, sourceSnapshotState: String(selected.sourceSnapshot?.state || '') }),
92
+ preflight,
93
93
  reasons
94
94
  });
95
95
  }
96
96
 
97
+ function projectExactLandingPreflight(workspaceId, provider, selected, findings) {
98
+ const sourceSnapshot = selected.sourceSnapshot;
99
+ if (!sourceSnapshot || String(sourceSnapshot.state || '') !== 'qualified' || !Array.isArray(sourceSnapshot.entries)) {
100
+ findings.push(finding('error', 'portable.workspace-landing.target-source-snapshot-required', 'Landing requires an exact target source-byte snapshot; repository clean state alone cannot prove that replacing the target will preserve newer or divergent work.', { workspaceId, repositoryId: selected.id, clean: selected.clean }));
101
+ return unavailablePreflight('target-source-snapshot-required');
102
+ }
103
+ const incoming = createPortableSourceFrontier({
104
+ id: `landing-incoming:${workspaceId}`,
105
+ source: { kind: 'qualified-handoff-package-workspace', workspaceId },
106
+ workspaces: [{ workspaceId, entries: (provider.entries || []).map(entryIdentity) }]
107
+ });
108
+ const current = createPortableSourceFrontier({
109
+ id: `landing-current:${workspaceId}`,
110
+ source: { kind: 'local-target-source-snapshot', repositoryId: selected.id, root: selected.root },
111
+ workspaces: [{ workspaceId, entries: sourceSnapshot.entries, evidence: sourceSnapshot.evidence || {} }]
112
+ });
113
+ const reconciliation = reconcilePortableSourceFrontiers({ base: incoming, incoming, current });
114
+ const workspace = (reconciliation.workspaces || []).find((item) => String(item.workspaceId || '') === workspaceId) || null;
115
+ if (reconciliation.status !== 'ready' || !workspace || ['qualification-error', 'unavailable', 'locked'].includes(String(workspace.state || ''))) {
116
+ findings.push(finding('error', 'portable.workspace-landing.target-source-snapshot-unqualified', 'Exact target source-byte preflight could not qualify the package/current frontier comparison; landing remains blocked.', { workspaceId, repositoryId: selected.id, reconciliationState: String(workspace?.state || reconciliation.state || '') }));
117
+ return freeze({
118
+ schema: 'tiinex.portable.workspace-landing-preflight.v1',
119
+ state: 'unresolved',
120
+ outcome: 'stop',
121
+ basis: 'accepted-recovery-as-base-and-incoming-vs-exact-current-target',
122
+ counts: freeze({}),
123
+ paths: freeze([]),
124
+ reconciliationState: String(workspace?.state || reconciliation.state || ''),
125
+ boundary: landingPreflightBoundary()
126
+ });
127
+ }
128
+ const paths = (workspace.paths || []).map((item) => freeze({
129
+ path: String(item.path || ''),
130
+ classification: String(item.classification || ''),
131
+ currentChange: String(item.currentChange || ''),
132
+ base: item.base || null,
133
+ incoming: item.incoming || null,
134
+ current: item.current || null
135
+ }));
136
+ if (!paths.length && String(workspace.state || '') === 'exact') {
137
+ return freeze({
138
+ schema: 'tiinex.portable.workspace-landing-preflight.v1',
139
+ state: 'exact-safe',
140
+ outcome: 'land',
141
+ basis: 'accepted-recovery-as-base-and-incoming-vs-exact-current-target',
142
+ counts: workspace.counts || freeze({ currentOnly: 0, conflictCandidate: 0, total: 0 }),
143
+ paths: freeze([]),
144
+ reconciliationState: 'exact',
145
+ boundary: landingPreflightBoundary()
146
+ });
147
+ }
148
+ const counts = workspace.counts || {};
149
+ findings.push(finding('error', 'portable.workspace-landing.reconciliation-required', 'Target source diverges from the accepted Recovery snapshot. Landing must stop before mutation and requires explicit reconciliation or preservation of the reported current-side changes.', {
150
+ workspaceId,
151
+ repositoryId: selected.id,
152
+ currentOnly: Number(counts.currentOnly || 0),
153
+ conflictCandidate: Number(counts.conflictCandidate || 0),
154
+ changedPaths: Number(counts.total || paths.length)
155
+ }));
156
+ return freeze({
157
+ schema: 'tiinex.portable.workspace-landing-preflight.v1',
158
+ state: 'reconciliation-required',
159
+ outcome: 'stop',
160
+ basis: 'accepted-recovery-as-base-and-incoming-vs-exact-current-target',
161
+ counts,
162
+ paths: freeze(paths),
163
+ reconciliationState: String(workspace.state || reconciliation.state || ''),
164
+ boundary: landingPreflightBoundary()
165
+ });
166
+ }
167
+
168
+ function unavailablePreflight(reason = '') {
169
+ return freeze({
170
+ schema: 'tiinex.portable.workspace-landing-preflight.v1',
171
+ state: 'unresolved',
172
+ outcome: 'stop',
173
+ reason: String(reason || ''),
174
+ basis: 'exact-target-source-required',
175
+ counts: freeze({}),
176
+ paths: freeze([]),
177
+ reconciliationState: 'unresolved',
178
+ boundary: landingPreflightBoundary()
179
+ });
180
+ }
181
+
97
182
  function result(status, inspection, workspaces, findings, input) {
98
183
  const affected = workspaces.filter((item) => item.state === 'ready');
99
184
  const untouched = workspaces.filter((item) => item.state !== 'ready');
@@ -109,12 +194,12 @@ function result(status, inspection, workspaces, findings, input) {
109
194
  required: affected.length > 0,
110
195
  repositoryCount: affected.length,
111
196
  repositoryRoots: freeze(affected.map((item) => String(item.repository?.root || '')).filter(Boolean)),
112
- statement: affected.length ? `Replace qualified non-ignored Workspace source in ${affected.length} local Git repositor${affected.length === 1 ? 'y' : 'ies'}; preserve .git and unrelated ignored local material; do not commit or push.` : '',
197
+ statement: affected.length ? `Exact source preflight passed for ${affected.length} local Git repositor${affected.length === 1 ? 'y' : 'ies'}; replace qualified non-ignored Workspace source, preserve .git and unrelated excluded local material, and do not commit or push.` : '',
113
198
  authority: 'explicit-human-host-confirmation-only'
114
199
  }),
115
200
  operationBoundary: freeze({ sourceMutation: false, remoteWrite: false, commit: false, push: false, acceptance: false }),
116
201
  findings: freeze(findings),
117
- boundary: 'Planning-only projection from one qualified Handoff package plus explicit local Git repository facts. Repository/ref matching is an operational safety constraint, not semantic authority. The plan does not extract, write, commit, push, accept, or complete any Workspace.'
202
+ boundary: 'Planning-only projection from one qualified Handoff package plus explicit local Git repository facts and exact target source-byte snapshots. Repository/ref matching and byte preflight are operational safety constraints, not semantic authority. Clean/dirty Git state alone never authorizes replacement. The plan does not extract, write, commit, push, accept, or complete any Workspace.'
118
203
  });
119
204
  }
120
205
 
@@ -128,10 +213,22 @@ function normalizeRepositories(value = []) {
128
213
  repository,
129
214
  repositoryIdentity: normalizeRepositoryIdentity(repository),
130
215
  branch: String(item?.branch || ''),
131
- clean: item?.clean === true ? true : item?.clean === false ? false : null
216
+ clean: item?.clean === true ? true : item?.clean === false ? false : null,
217
+ sourceSnapshot: normalizeSourceSnapshot(item?.sourceSnapshot || item?.snapshot || null)
132
218
  });
133
219
  }));
134
220
  }
221
+ function normalizeSourceSnapshot(value) {
222
+ if (!value || typeof value !== 'object') return null;
223
+ return freeze({
224
+ state: String(value.state || (Array.isArray(value.entries) ? 'qualified' : '')),
225
+ entries: Array.isArray(value.entries) ? value.entries.map((entry) => entryIdentity(entry)) : [],
226
+ evidence: value.evidence && typeof value.evidence === 'object' ? { ...value.evidence } : {},
227
+ findings: Array.isArray(value.findings) ? value.findings.map((item) => ({ ...item })) : []
228
+ });
229
+ }
230
+ function entryIdentity(entry = {}) { return freeze({ path: String(entry.path || entry.innerPath || ''), bytes: Number(entry.bytes ?? entry.size ?? 0), sha256: String(entry.sha256 || '').toLowerCase() }); }
231
+ function landingPreflightBoundary() { return 'Read-only Recovery landing safety proof. The accepted carried Workspace is used as both base and incoming so every target deviation is current-side divergence; explicit reconciliation remains separate and no mutation or semantic merge is performed.'; }
135
232
  function normalizeSelections(value = {}) {
136
233
  const source = value?.selections && typeof value.selections === 'object' ? value.selections : value;
137
234
  return Object.fromEntries(Object.entries(source || {}).map(([key, selected]) => [normalizeId(key), String(selected || '')]).filter(([key, selected]) => key && selected));
@@ -7,6 +7,7 @@ import { projectPortableHandoffCarrierOutputFromPackage } from './handoff/recipi
7
7
  import { orientColdConsumerFromHandoffPackage } from './handoff/coldConsumerEntrypoint.js';
8
8
  import { auditHandoffPackageContextCarriage } from './handoff/contextAudit.js';
9
9
  import { projectPortableWorkspaceLandingPlan } from './handoff/workspaceLandingPlan.js';
10
+ import { auditPortableRecoveryAcceptance } from './handoff/recoveryAcceptanceAudit.js';
10
11
  import { projectPortableEditorAssistance } from './editor/editor.assistance.js';
11
12
  import { projectQualifiedHandoffLeaves } from './handoff/handoffLeafProjection.js';
12
13
  import { projectPortableAuthoringParent } from './editor/authoring.parent.js';
@@ -62,11 +63,18 @@ export function createPortablePackageOperationEntries({ operation, wrapPortableR
62
63
  }),
63
64
  'project-workspace-landing': operation({
64
65
  name: 'project-workspace-landing',
65
- description: 'Project qualified carried Workspaces onto explicit local Git repository facts for one fail-closed human landing confirmation without extracting, writing, committing, pushing, or creating semantic authority.',
66
+ description: 'Project qualified carried Workspaces onto explicit local Git repository facts plus exact target source-byte snapshots, failing closed on any target divergence before extraction or mutation.',
66
67
  safety: 'planning-only-read-only',
67
68
  inputSchema: 'tiinex.portable.workspace-landing-plan.request.v1',
68
69
  handler: (input = {}) => wrapPortableResult('project-workspace-landing', projectPortableWorkspaceLandingPlan(input))
69
70
  }),
71
+ 'audit-recovery-acceptance': operation({
72
+ name: 'audit-recovery-acceptance',
73
+ description: 'Audit one qualified candidate Recovery carrier against one explicit accepted-basis carrier for complete Workspace re-materialization and unexplained source removals without granting acceptance.',
74
+ safety: 'read-only-mechanical-audit',
75
+ inputSchema: 'tiinex.portable.recovery-acceptance-audit.request.v1',
76
+ handler: (input = {}) => wrapPortableResult('audit-recovery-acceptance', auditPortableRecoveryAcceptance(input))
77
+ }),
70
78
  'project-editor-assistance': operation({
71
79
  name: 'project-editor-assistance',
72
80
  description: 'Project exact shared validator findings, deterministic source locations where provable, and bounded deterministic hygiene repairs for editor hosts.',