@tiinex/core 0.13.0 → 0.15.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 (29) hide show
  1. package/package.json +5 -5
  2. package/src/integrity/integrity.c14nV1.js +28 -0
  3. package/src/lineage/lineage.integrity.js +108 -22
  4. package/src/lineage/lineage.resolve.js +21 -14
  5. package/src/lineage/lineage.sourceScope.js +41 -12
  6. package/src/lineage/lineage.targetKeys.js +26 -7
  7. package/src/tooling/portable/adapters/cli/cli.handoff-manufacture.js +47 -10
  8. package/src/tooling/portable/adapters/node/bootstrapCarrier.manufacture.js +18 -0
  9. package/src/tooling/portable/adapters/node/workspaceCarrier.manufacture.js +13 -3
  10. package/src/tooling/portable/bootstrap/tiinex.llm.bootstrap.md +2 -2
  11. package/src/tooling/portable/handoff/bootstrapCarrier.manufacture.js +56 -0
  12. package/src/tooling/portable/handoff/contracts/tiinex.handoff.package.v1.schema.md +340 -77
  13. package/src/tooling/portable/handoff/manufacture.js +3 -1
  14. package/src/tooling/portable/handoff/recipientV2.artifactFirst.build.js +73 -32
  15. package/src/tooling/portable/handoff/recipientV2.artifactFirst.closure.js +1 -1
  16. package/src/tooling/portable/handoff/recipientV2.artifactFirst.inspect.js +9 -1
  17. package/src/tooling/portable/handoff/recipientV2.artifactFirst.materials.js +28 -6
  18. package/src/tooling/portable/handoff/recipientV2.artifactFirst.shared.js +8 -0
  19. package/src/tooling/portable/handoff/recipientV2.coldProjection.js +11 -6
  20. package/src/tooling/portable/handoff/recipientV2.entryContract.js +12 -0
  21. package/src/tooling/portable/handoff/recipientV2.humanOutput.js +48 -6
  22. package/src/tooling/portable/handoff/recipientV2.packageV1.build.js +55 -43
  23. package/src/tooling/portable/handoff/recipientV2.packageV1.contract.js +57 -31
  24. package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.helpers.js +63 -10
  25. package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.js +55 -16
  26. package/src/tooling/portable/handoff/recipientV2.packageV1.workspaceProjection.js +14 -1
  27. package/src/tooling/portable/handoff/recipientV2.topology.js +3 -4
  28. package/src/tooling/portable/handoff/recipientV2.topology.materials.js +14 -0
  29. package/src/tooling/portable/handoff/workspaceCarrier.manufacture.js +42 -13
@@ -4,9 +4,11 @@ import { upgradeRecipientRelativeHandoffTransportPackageV2 } from './materialClo
4
4
  import { buildRecipientRelativeHandoffV2DirectBaseline } from './materialClosure.archiveV2.direct.js';
5
5
  import { qualifyMajorCarrierReadiness } from './carrierLineage.js';
6
6
  import { manufactureRecipientRelativeWorkspacePackage } from './workspaceCarrier.manufacture.js';
7
+ import { manufactureRecipientRelativeBootstrapPackage } from './bootstrapCarrier.manufacture.js';
7
8
 
8
9
  export function manufactureRecipientRelativeHandoffPackage(input = {}, options = {}) {
9
10
  if (String(input.carrierMode || '') === 'workspace') return manufactureRecipientRelativeWorkspacePackage(input, options);
11
+ if (String(input.carrierMode || '') === 'bootstrap') return manufactureRecipientRelativeBootstrapPackage(input, options);
10
12
  const baseline = buildRecipientRelativeHandoffV2DirectBaseline(input, options);
11
13
  const upgraded = upgradeRecipientRelativeHandoffTransportPackageV2(baseline, input, options);
12
14
  const toolingBootstrapInspection = upgraded.inspection?.bootstrapInspection || inspectPortableToolingBootstrap(baseline.bundle || upgraded.bundle || {});
@@ -91,6 +93,6 @@ export function manufactureRecipientRelativeHandoffPackage(input = {}, options =
91
93
  baseline: Object.freeze({ schema: baseline.schema, status: baseline.status, packageRepresentationSha256: String(baseline.bundle?.packageRepresentationSha256 || ''), representation: 'semantic-control-plus-detached-material-without-exploded-workspace-carrier' }),
92
94
  findings,
93
95
  findingSummary: summarizePortableFindings(findings),
94
- boundary: 'Canonical archive-backed Handoff manufacturing facade. It fails closed unless each carrier workspace is bound to one exact carried tiinex.workspace.v1 artifact and one independently verified complete workspace archive; the actively selected local Handoff candidate must satisfy prospective per-field exact schema-reference authority, and when reconciliation proof is required/supplied, the exact source selected for manufacture must also match the qualified candidate reconciled frontier.'
96
+ boundary: 'Canonical archive-backed Handoff manufacturing facade. It fails closed unless every selected source-material binding qualifies under tiinex.handoff.package.v1, the exact authoritative selected Handoff is contained by clear qualified carried material, the actively selected local Handoff candidate satisfies prospective per-field exact schema-reference authority, and any required reconciliation proof matches the exact source selected for manufacture.'
95
97
  });
96
98
  }
@@ -9,7 +9,7 @@ import { buildPhase1WorkspaceCarriers } from './recipientV2.artifactFirst.worksp
9
9
  import { buildPhase1RolePointers } from './recipientV2.artifactFirst.roles.js';
10
10
  import { deriveRecipientV2ArtifactFirstPhase1Facts } from './recipientV2.artifactFirst.materials.js';
11
11
  import { inspectRecipientV2ArtifactFirstPhase1Specimen } from './recipientV2.artifactFirst.inspect.js';
12
- import { RECIPIENT_V2_READ_PATH, RECIPIENT_V2_ARTIFACT_FIRST_PHASE1_FORMAT_ID, RECIPIENT_V2_ARTIFACT_FIRST_PHASE1_SCHEMA_ID, RECIPIENT_V2_ARTIFACT_FIRST_PHASE2_CLEAN_FORMAT_ID, RECIPIENT_V2_ARTIFACT_FIRST_PHASE2_CLEAN_SCHEMA_ID, PHASE2_CLEAN_PROFILE, PHASE2_COMPATIBILITY_TRANSPORT, PHASE1_BOOTSTRAP_ROLE, PHASE1_CACHE_ROLE, normalizeRoutePath, cacheMaterialBelongsToRoute, sortedQualifiedCarrierRoutes, selectOne, oneFile, repathFinalizedFile, phase1RoutePointerPath, phase1RolePathPrefix, blocked, finding, deepFreeze } from './recipientV2.artifactFirst.shared.js';
12
+ import { RECIPIENT_V2_READ_PATH, RECIPIENT_V2_ARTIFACT_FIRST_PHASE1_FORMAT_ID, RECIPIENT_V2_ARTIFACT_FIRST_PHASE1_SCHEMA_ID, RECIPIENT_V2_ARTIFACT_FIRST_PHASE2_CLEAN_FORMAT_ID, RECIPIENT_V2_ARTIFACT_FIRST_PHASE2_CLEAN_SCHEMA_ID, PHASE2_CLEAN_PROFILE, PHASE2_COMPATIBILITY_TRANSPORT, PHASE1_BOOTSTRAP_ROLE, PHASE1_CACHE_ROLE, normalizeRoutePath, cacheMaterialBelongsToRoute, cacheMaterialIsBoundedWorkspaceRecovery, safeToken, sortedQualifiedCarrierRoutes, selectOne, oneFile, repathFinalizedFile, phase1RoutePointerPath, phase1RolePathPrefix, blocked, finding, deepFreeze } from './recipientV2.artifactFirst.shared.js';
13
13
 
14
14
  export function buildRecipientV2ArtifactFirstPhase1Specimen(input = {}) {
15
15
  const cleanCarrierPhase2 = input.cleanCarrierPhase2 === true;
@@ -103,60 +103,100 @@ export function buildRecipientV2ArtifactFirstPhase1Specimen(input = {}) {
103
103
  })
104
104
  }) : null;
105
105
 
106
- const sourceCache = (sourceInspection.caches || []).find((item) => String(item.workspaceId || '') === workspaceId) || null;
107
- const selectedCacheMaterials = sourceCache ? (sourceCache.materials || []).filter((item) => cacheMaterialBelongsToRoute(item, workspaceId, route.workspaceRelativeHandoffPath)) : [];
106
+ const sourceCaches = [...(sourceInspection.caches || [])];
107
+ const sourceCache = sourceCaches.find((item) => String(item.workspaceId || '') === workspaceId) || null;
108
+ const selectedRouteCacheMaterials = sourceCache ? (sourceCache.materials || []).filter((item) => cacheMaterialBelongsToRoute(item, workspaceId, route.workspaceRelativeHandoffPath)) : [];
108
109
  const siblingDetachedMaterials = routePlans
109
110
  .filter((plan) => plan.routeId !== selectedRouteId)
110
- .flatMap((plan) => (sourceInspection.caches || []).flatMap((cache) => (cache.materials || []).filter((item) => cacheMaterialBelongsToRoute(item, plan.workspaceId, plan.workspaceRelativeHandoffPath))));
111
- if (siblingDetachedMaterials.length) return blocked('shared-route-detached-cache-unsupported', [finding('error', 'portable.handoff-v2-phase1.shared-route.detached-cache-unsupported', 'Shared artifact-first carriage currently supports detached cache material only for the explicitly selected route; sibling routes must resolve closure from carried Workspace source.', { count: siblingDetachedMaterials.length })]);
111
+ .flatMap((plan) => sourceCaches.flatMap((cache) => (cache.materials || []).filter((item) => cacheMaterialBelongsToRoute(item, plan.workspaceId, plan.workspaceRelativeHandoffPath) && !cacheMaterialIsBoundedWorkspaceRecovery(item, plan.workspaceId))));
112
+ if (siblingDetachedMaterials.length) return blocked('shared-route-detached-cache-unsupported', [finding('error', 'portable.handoff-v2-phase1.shared-route.detached-cache-unsupported', 'Shared artifact-first carriage currently supports route-specific detached cache material only for the explicitly selected route; bounded Workspace recovery remains independently carryable for every bounded Workspace.', { count: siblingDetachedMaterials.length })]);
113
+
114
+ const cacheBuilds = [];
112
115
  let cacheArtifact = null;
113
- let cacheArchiveFile = null;
114
- let cacheProjection = null;
115
- if (selectedCacheMaterials.length) {
116
- const sourceCacheArchive = oneFile(sourceBundle.files || [], sourceCache.archivePath);
117
- if (!sourceCacheArchive) return blocked('cache-source-unresolved', [finding('error', 'portable.handoff-v2-phase1.cache.source-unresolved', 'Selected-route detached cache archive is unavailable in the qualified source carrier.', { path: sourceCache.archivePath })]);
116
+ let selectedCacheMaterials = [];
117
+ let auxiliaryCacheOrdinal = 0;
118
+ for (const candidateCache of sourceCaches) {
119
+ const cacheWorkspaceId = String(candidateCache.workspaceId || '');
120
+ const recoveryMaterials = (candidateCache.materials || []).filter((item) => cacheMaterialIsBoundedWorkspaceRecovery(item, cacheWorkspaceId));
121
+ const routeMaterials = cacheWorkspaceId === workspaceId ? selectedRouteCacheMaterials : [];
122
+ const byMaterialKey = new Map();
123
+ for (const material of [...routeMaterials, ...recoveryMaterials]) {
124
+ const key = `${String(material.requirementId || '')}\u0000${String(material.archiveEntry || '')}`;
125
+ if (!byMaterialKey.has(key)) byMaterialKey.set(key, material);
126
+ }
127
+ const carriedMaterials = [...byMaterialKey.values()];
128
+ if (!carriedMaterials.length) continue;
129
+
130
+ const sourceCacheArchive = oneFile(sourceBundle.files || [], candidateCache.archivePath);
131
+ if (!sourceCacheArchive) return blocked('cache-source-unresolved', [finding('error', 'portable.handoff-v2-phase1.cache.source-unresolved', 'Detached cache archive is unavailable in the qualified source carrier.', { workspaceId: cacheWorkspaceId, path: candidateCache.archivePath })]);
118
132
  const sourceCacheInspection = inspectStoredWorkspaceArchive(packageFileBytes(sourceCacheArchive), { ownedBytes: true });
119
- if (sourceCacheInspection.state !== 'qualified') return blocked('cache-source-invalid', [finding('error', 'portable.handoff-v2-phase1.cache.source-invalid', 'Selected-route detached cache archive did not independently qualify.', { path: sourceCache.archivePath, findings: sourceCacheInspection.findings || [] })]);
133
+ if (sourceCacheInspection.state !== 'qualified') return blocked('cache-source-invalid', [finding('error', 'portable.handoff-v2-phase1.cache.source-invalid', 'Detached cache archive did not independently qualify.', { workspaceId: cacheWorkspaceId, path: candidateCache.archivePath, findings: sourceCacheInspection.findings || [] })]);
134
+
120
135
  const selectedEntries = [];
121
136
  const materialBindings = [];
122
- for (const material of selectedCacheMaterials) {
137
+ for (const material of carriedMaterials) {
123
138
  const archiveEntry = String(material.archiveEntry || '');
124
139
  const matches = sourceCacheInspection.entries.filter((entry) => String(entry.path || '') === archiveEntry);
125
- if (matches.length !== 1) return blocked('cache-material-unresolved', [finding('error', 'portable.handoff-v2-phase1.cache.material-unresolved', 'Selected-route cache material must resolve exactly one source cache entry.', { requirementId: String(material.requirementId || ''), referenceTarget: String(material.referenceTarget || ''), archiveEntry, count: matches.length })]);
140
+ if (matches.length !== 1) return blocked('cache-material-unresolved', [finding('error', 'portable.handoff-v2-phase1.cache.material-unresolved', 'Carried cache material must resolve exactly one source cache entry.', { workspaceId: cacheWorkspaceId, requirementId: String(material.requirementId || ''), referenceTarget: String(material.referenceTarget || ''), archiveEntry, count: matches.length })]);
126
141
  selectedEntries.push({ path: archiveEntry, data: matches[0].data });
127
- materialBindings.push(Object.freeze({ requirementId: String(material.requirementId || ''), classification: String(material.classification || ''), referenceTarget: String(material.referenceTarget || ''), archiveEntry }));
142
+ materialBindings.push(Object.freeze({
143
+ requirementId: String(material.requirementId || ''),
144
+ classification: String(material.classification || ''),
145
+ referenceTarget: String(material.referenceTarget || ''),
146
+ routeWorkspaceId: String(material.routeWorkspaceId || ''),
147
+ routePath: String(material.routePath || ''),
148
+ sourceRequirementId: String(material.sourceRequirementId || ''),
149
+ sourceWorkspaceId: String(material.sourceWorkspaceId || ''),
150
+ sourcePath: String(material.sourcePath || ''),
151
+ targetWorkspaceId: String(material.targetWorkspaceId || ''),
152
+ targetPath: String(material.targetPath || ''),
153
+ originalPath: String(material.originalPath || ''),
154
+ archiveEntry,
155
+ bytes: Number(material.bytes || matches[0].bytes || 0),
156
+ sha256: String(material.sha256 || matches[0].sha256 || '')
157
+ }));
128
158
  }
129
- const cacheArchivePath = '001-5-cache.zip';
130
- const cacheArtifactPath = '001-5-cache.trace.md';
131
- cacheArchiveFile = finalizeFile({
132
- path: cacheArchivePath,
159
+
160
+ const selectedWorkspaceCache = cacheWorkspaceId === workspaceId;
161
+ auxiliaryCacheOrdinal += selectedWorkspaceCache ? 0 : 1;
162
+ const cacheStem = selectedWorkspaceCache ? '001-5-cache' : `001-5-${auxiliaryCacheOrdinal}-${safeToken(cacheWorkspaceId)}-recovery-cache`;
163
+ const builtArchivePath = `${cacheStem}.zip`;
164
+ const builtArtifactPath = `${cacheStem}.trace.md`;
165
+ const builtArchiveFile = finalizeFile({
166
+ path: builtArchivePath,
133
167
  kind: 'handoff-material-cache',
134
168
  logicalKind: 'recipient-v2-phase1-workspace-dependency-cache',
135
169
  mediaType: 'application/zip',
136
170
  data: exportFileMapZipUint8Array(selectedEntries, 'portable.handoff-v2-phase1.cache.path.invalid'),
137
- boundary: 'Exact selected-route detached dependency bytes owned by the visible cache External Payload artifact. Cache location and compatibility JSON are not semantic authority.'
171
+ boundary: 'Exact route-scoped detached dependency and/or bounded Workspace Parent-recovery bytes owned by one visible cache External Payload artifact. Cache location and compatibility JSON are not semantic authority.'
138
172
  });
139
- cacheArtifact = finalizeFile({
140
- path: cacheArtifactPath,
173
+ const builtArtifact = finalizeFile({
174
+ path: builtArtifactPath,
141
175
  kind: 'tiinex-external-payload-artifact',
142
176
  logicalKind: 'recipient-v2-phase1-workspace-dependency-cache-reference',
143
177
  mediaType: 'text/markdown',
144
178
  content: renderRecipientV2ExternalPayload({
145
179
  artifactFirst: true,
146
180
  createdAt,
147
- workspaceId,
148
- title: `Workspace Dependency Cache — ${workspaceId}`,
149
- summary: 'Artifact-first Phase 1 exact selected-route dependency bytes absent from the qualified Workspace payload.',
150
- label: `${workspaceId} selected-route Handoff dependency cache`,
181
+ workspaceId: cacheWorkspaceId,
182
+ title: `Workspace Dependency Cache — ${cacheWorkspaceId}`,
183
+ summary: 'Artifact-first exact detached dependency and bounded Parent-recovery bytes absent from the qualified Workspace payload.',
184
+ label: `${cacheWorkspaceId} Handoff dependency and recovery cache`,
151
185
  kind: 'zip export',
152
186
  role: PHASE1_CACHE_ROLE,
153
- location: cacheArchivePath,
154
- bytes: cacheArchiveFile.bytes,
155
- sha256: cacheArchiveFile.sha256,
187
+ location: builtArchivePath,
188
+ bytes: builtArchiveFile.bytes,
189
+ sha256: builtArchiveFile.sha256,
156
190
  materials: materialBindings
157
191
  })
158
192
  });
159
- cacheProjection = Object.freeze({ workspaceId, artifactPath: cacheArtifactPath, archivePath: cacheArchivePath, materials: Object.freeze(materialBindings) });
193
+ const projection = Object.freeze({ workspaceId: cacheWorkspaceId, artifactPath: builtArtifactPath, archivePath: builtArchivePath, materials: Object.freeze(materialBindings) });
194
+ const built = Object.freeze({ workspaceId: cacheWorkspaceId, artifact: builtArtifact, archiveFile: builtArchiveFile, projection, sourceMaterials: Object.freeze(carriedMaterials) });
195
+ cacheBuilds.push(built);
196
+ if (selectedWorkspaceCache) {
197
+ cacheArtifact = builtArtifact;
198
+ selectedCacheMaterials = carriedMaterials;
199
+ }
160
200
  }
161
201
 
162
202
  const endpointRoleArtifacts = [];
@@ -257,7 +297,7 @@ export function buildRecipientV2ArtifactFirstPhase1Specimen(input = {}) {
257
297
  destinations: [
258
298
  ...(bootstrapArtifact ? [{ label: 'Portable Tooling bootstrap payload', target: bootstrapArtifactPath }] : []),
259
299
  ...workspaceCarriers.map((item) => ({ label: `${item.workspaceId} Workspace payload`, target: item.payloadArtifactPath })),
260
- ...(cacheArtifact ? [{ label: 'Selected-route dependency cache payload', target: cacheArtifact.path }] : []),
300
+ ...cacheBuilds.map((item) => ({ label: `Workspace dependency/recovery cache ${item.workspaceId}`, target: item.artifact.path })),
261
301
  ...workspaceCarriers.map((item) => ({ label: `${item.workspaceId} Workspace material representation`, target: item.relationArtifactPath })),
262
302
  ...endpointRoleArtifacts.map((item) => ({ label: 'Endpoint Role pointer', target: item.path })),
263
303
  ...participantRoleArtifacts.map((item) => ({ label: 'Participant Role pointer', target: item.path })),
@@ -267,7 +307,8 @@ export function buildRecipientV2ArtifactFirstPhase1Specimen(input = {}) {
267
307
  });
268
308
 
269
309
  const workspaceFiles = workspaceCarriers.flatMap((item) => [item.payloadArtifact, item.archiveFile, item.relationArtifact]);
270
- const semanticFiles = Object.freeze([ingressPointer, ...(bootstrapArtifact && bootstrapArchiveFile ? [bootstrapArtifact, bootstrapArchiveFile] : []), ...workspaceFiles, ...(cacheArtifact && cacheArchiveFile ? [cacheArtifact, cacheArchiveFile] : []), ...endpointRoleArtifacts, ...participantRoleArtifacts, ...routePointers]);
310
+ const cacheFiles = cacheBuilds.flatMap((item) => [item.artifact, item.archiveFile]);
311
+ const semanticFiles = Object.freeze([ingressPointer, ...(bootstrapArtifact && bootstrapArchiveFile ? [bootstrapArtifact, bootstrapArchiveFile] : []), ...workspaceFiles, ...cacheFiles, ...endpointRoleArtifacts, ...participantRoleArtifacts, ...routePointers]);
271
312
  const factsByPath = deriveRecipientV2ArtifactFirstPhase1Facts(semanticFiles);
272
313
  const manifestInputs = semanticFiles.map((file) => {
273
314
  const facts = factsByPath.get(String(file.path || '')) || null;
@@ -293,7 +334,7 @@ export function buildRecipientV2ArtifactFirstPhase1Specimen(input = {}) {
293
334
  sourceWorkspaceTargetInnerPath: String(item.workspace.sourceWorkspaceTargetInnerPath || ''),
294
335
  sourceWorkspaceTargetSha256: String(item.workspace.sourceWorkspaceTargetSha256 || '')
295
336
  }))),
296
- caches: Object.freeze(cacheProjection ? [cacheProjection] : []),
337
+ caches: Object.freeze(cacheBuilds.map((item) => item.projection)),
297
338
  endpointRoles: Object.freeze(endpointRoleProjections),
298
339
  participantRoles: Object.freeze(participantRoleProjections),
299
340
  routes: Object.freeze(routeTopology),
@@ -124,7 +124,7 @@ export function phase1CacheParentCandidates(cacheQualifications = []) {
124
124
  const entry = matches[0];
125
125
  const data = packageFileBytes({ data: entry.data });
126
126
  if (Number(material.bytes || 0) !== data.byteLength || String(material.sha256 || '') !== sha256Hex(data)) continue;
127
- candidates.push(Object.freeze({ workspaceId: String(cache.workspaceId || ''), workspaceRelativePath: normalizeRoutePath(material.referenceTarget || ''), archiveEntry: String(material.archiveEntry || ''), bytes: data.byteLength, sha256: sha256Hex(data), data }));
127
+ candidates.push(Object.freeze({ workspaceId: String(material.targetWorkspaceId || cache.workspaceId || ''), workspaceRelativePath: normalizeRoutePath(material.originalPath || material.targetPath || material.referenceTarget || ''), archiveEntry: String(material.archiveEntry || ''), bytes: data.byteLength, sha256: sha256Hex(data), data }));
128
128
  }
129
129
  }
130
130
  return Object.freeze(candidates);
@@ -34,7 +34,15 @@ export function inspectRecipientV2ArtifactFirstPhase1Specimen(bundle = {}) {
34
34
  if (!routes.length) findings.push(finding('error', 'portable.handoff-v2-phase1.route.count', 'Artifact-first specimen requires at least one visible Handoff route Pointer.', { count: routes.length }));
35
35
  if (!workspacePayloads.length) findings.push(finding('error', 'portable.handoff-v2-phase1.payload.workspace-count', 'Artifact-first full-source carriage requires at least one Workspace External Payload artifact.', { count: workspacePayloads.length }));
36
36
  if (bootstrapPayloads.length > 1) findings.push(finding('error', 'portable.handoff-v2-phase1.bootstrap.count', 'A carried portable Tooling bootstrap must have exactly one owning External Payload artifact.', { count: bootstrapPayloads.length }));
37
- if (cachePayloads.length > 1) findings.push(finding('error', 'portable.handoff-v2-phase1.cache.count', 'A selected Workspace may expose at most one selected-route dependency cache External Payload.', { count: cachePayloads.length }));
37
+ const cachePayloadsByWorkspace = new Map();
38
+ for (const item of cachePayloads) {
39
+ const cacheWorkspaceId = String(item.parsed?.workspaceId || '');
40
+ if (!cacheWorkspaceId) findings.push(finding('error', 'portable.handoff-v2-phase1.cache.workspace-id-missing', 'Every dependency/recovery cache External Payload must visibly declare its owning Workspace Id.', { path: item.path }));
41
+ const list = cachePayloadsByWorkspace.get(cacheWorkspaceId) || [];
42
+ list.push(item);
43
+ cachePayloadsByWorkspace.set(cacheWorkspaceId, list);
44
+ }
45
+ for (const [cacheWorkspaceId, items] of cachePayloadsByWorkspace) if (cacheWorkspaceId && items.length > 1) findings.push(finding('error', 'portable.handoff-v2-phase1.cache.count', 'Each carried Workspace may expose at most one dependency/recovery cache External Payload.', { workspaceId: cacheWorkspaceId, count: items.length }));
38
46
  if (unknownPayloads.length) findings.push(finding('error', 'portable.handoff-v2-phase1.payload.unclassified', 'Phase 1 specimen contains an External Payload artifact outside the bounded Workspace/bootstrap/cache ownership subset.', { count: unknownPayloads.length }));
39
47
  if (!relations.length) findings.push(finding('error', 'portable.handoff-v2-phase1.relation.count', 'Artifact-first full-source carriage requires a typed material-representation Relation for every carried Workspace.', { count: relations.length }));
40
48
  if (markdownFiles.some((file) => factsByPath.get(String(file.path || ''))?.role === 'package-root')) findings.push(finding('error', 'portable.handoff-v2-phase1.package-root.present', 'Phase 1 specimen must not use a package-root Pointer as receiver semantic authority.'));
@@ -75,7 +75,7 @@ export function deriveRecipientV2ArtifactFirstPhase1Facts(files = []) {
75
75
  const matches = archive?.state === 'qualified' ? archive.entries.filter((entry) => String(entry.path || '') === String(material.archiveEntry || '')) : [];
76
76
  const entry = matches.length === 1 ? matches[0] : null;
77
77
  const bytes = entry ? packageFileBytes({ data: entry.data }) : new Uint8Array();
78
- return Object.freeze({ requirementId: material.requirementId, classification: material.classification, referenceTarget: material.referenceTarget, routeWorkspaceId: parsed.workspaceId, routePath: '', sourceRequirementId: material.requirementId, originalPath: '', archiveEntry: material.archiveEntry, bytes: bytes.byteLength, sha256: bytes.byteLength ? sha256Hex(bytes) : '' });
78
+ return Object.freeze({ requirementId: material.requirementId, classification: material.classification, referenceTarget: material.referenceTarget, routeWorkspaceId: material.routeWorkspaceId || parsed.workspaceId, routePath: material.routePath || '', sourceRequirementId: material.sourceRequirementId || material.requirementId, sourceWorkspaceId: material.sourceWorkspaceId || '', sourcePath: material.sourcePath || '', targetWorkspaceId: material.targetWorkspaceId || '', targetPath: material.targetPath || '', originalPath: material.originalPath || '', archiveEntry: material.archiveEntry, bytes: bytes.byteLength, sha256: bytes.byteLength ? sha256Hex(bytes) : '' });
79
79
  });
80
80
  facts.set(path, recipientV2TransportFacts('workspace-dependency-cache', { workspaceId: parsed.workspaceId, archivePath: parsed.location || '', archiveBytes: Number(parsed.bytes || 0), archiveSha256: parsed.integrityValue || '', materials: Object.freeze(materials) }));
81
81
  }
@@ -115,16 +115,36 @@ function parsePhase1MaterialBindings(markdown = '') {
115
115
  const requirement = line.match(/^\s*-\s+Requirement Id:\s*(.*?)\s*$/i);
116
116
  if (requirement) {
117
117
  if (current) out.push(Object.freeze(current));
118
- current = { requirementId: String(requirement[1] || '').trim(), classification: '', referenceTarget: '', archiveEntry: '' };
118
+ current = { requirementId: String(requirement[1] || '').trim(), classification: '', referenceTarget: '', routeWorkspaceId: '', routePath: '', sourceRequirementId: '', sourceWorkspaceId: '', sourcePath: '', targetWorkspaceId: '', targetPath: '', originalPath: '', archiveEntry: '', bytes: null, sha256: '' };
119
119
  continue;
120
120
  }
121
121
  if (!current) continue;
122
122
  const classification = line.match(/^\s+-\s+Classification:\s*(.*?)\s*$/i);
123
123
  const reference = line.match(/^\s+-\s+Material Reference:\s*(.*?)\s*$/i);
124
124
  const archiveEntry = line.match(/^\s+-\s+Archive Entry:\s*(.*?)\s*$/i);
125
+ const routeWorkspaceId = line.match(/^\s+-\s+Route Workspace Id:\s*(.*?)\s*$/i);
126
+ const routePath = line.match(/^\s+-\s+Route Path:\s*(.*?)\s*$/i);
127
+ const sourceRequirementId = line.match(/^\s+-\s+Source Requirement Id:\s*(.*?)\s*$/i);
128
+ const sourceWorkspaceId = line.match(/^\s+-\s+Source Workspace Id:\s*(.*?)\s*$/i);
129
+ const sourcePath = line.match(/^\s+-\s+Source Path:\s*(.*?)\s*$/i);
130
+ const targetWorkspaceId = line.match(/^\s+-\s+Target Workspace Id:\s*(.*?)\s*$/i);
131
+ const targetPath = line.match(/^\s+-\s+Target Path:\s*(.*?)\s*$/i);
132
+ const originalPath = line.match(/^\s+-\s+Original Path:\s*(.*?)\s*$/i);
133
+ const bytes = line.match(/^\s+-\s+Byte Size:\s*(.*?)\s*$/i);
134
+ const sha256 = line.match(/^\s+-\s+SHA256:\s*(.*?)\s*$/i);
125
135
  if (classification) current.classification = String(classification[1] || '').trim();
126
136
  else if (reference) current.referenceTarget = String(reference[1] || '').trim();
127
137
  else if (archiveEntry) current.archiveEntry = String(archiveEntry[1] || '').trim();
138
+ else if (routeWorkspaceId) current.routeWorkspaceId = String(routeWorkspaceId[1] || '').trim();
139
+ else if (routePath) current.routePath = String(routePath[1] || '').trim();
140
+ else if (sourceRequirementId) current.sourceRequirementId = String(sourceRequirementId[1] || '').trim();
141
+ else if (sourceWorkspaceId) current.sourceWorkspaceId = String(sourceWorkspaceId[1] || '').trim();
142
+ else if (sourcePath) current.sourcePath = String(sourcePath[1] || '').trim();
143
+ else if (targetWorkspaceId) current.targetWorkspaceId = String(targetWorkspaceId[1] || '').trim();
144
+ else if (targetPath) current.targetPath = String(targetPath[1] || '').trim();
145
+ else if (originalPath) current.originalPath = String(originalPath[1] || '').trim();
146
+ else if (bytes) current.bytes = Number(bytes[1] || 0);
147
+ else if (sha256) current.sha256 = String(sha256[1] || '').trim();
128
148
  }
129
149
  if (current) out.push(Object.freeze(current));
130
150
  return Object.freeze(out);
@@ -183,7 +203,6 @@ export function qualifyPhase1CachePayload(cachePayload = null, semanticFiles = [
183
203
  if (archive.state !== 'qualified') localFindings.push(finding('error', 'portable.handoff-v2-phase1.cache.archive-invalid', 'Selected-route cache payload is not a qualified deterministic stored-ZIP representation.', { path: location, findings: archive.findings || [] }));
184
204
  }
185
205
  const seenRequirements = new Set();
186
- const seenReferences = new Set();
187
206
  const seenEntries = new Set();
188
207
  for (const material of cachePayload.parsed?.materials || []) {
189
208
  const requirementId = String(material.requirementId || '');
@@ -193,13 +212,16 @@ export function qualifyPhase1CachePayload(cachePayload = null, semanticFiles = [
193
212
  localFindings.push(finding('error', 'portable.handoff-v2-phase1.cache.material-binding-incomplete', 'Every selected-route cache material binding must visibly declare requirement id, material reference, and archive entry.'));
194
213
  continue;
195
214
  }
196
- if (seenRequirements.has(requirementId) || seenReferences.has(referenceTarget) || seenEntries.has(archiveEntry)) localFindings.push(finding('error', 'portable.handoff-v2-phase1.cache.material-binding-ambiguous', 'Selected-route cache material bindings must be one-to-one by requirement id, material reference, and archive entry.', { requirementId, referenceTarget, archiveEntry }));
197
- seenRequirements.add(requirementId); seenReferences.add(referenceTarget); seenEntries.add(archiveEntry);
215
+ if (seenRequirements.has(requirementId) || seenEntries.has(archiveEntry)) localFindings.push(finding('error', 'portable.handoff-v2-phase1.cache.material-binding-ambiguous', 'Cache material bindings must be one-to-one by requirement id and archive entry; multiple requirements may legitimately share the same Parent reference.', { requirementId, referenceTarget, archiveEntry }));
216
+ seenRequirements.add(requirementId); seenEntries.add(archiveEntry);
198
217
  const entryMatches = archive?.state === 'qualified' ? archive.entries.filter((entry) => String(entry.path || '') === archiveEntry) : [];
199
218
  if (archive?.state === 'qualified' && entryMatches.length !== 1) localFindings.push(finding('error', 'portable.handoff-v2-phase1.cache.material-entry-unresolved', 'Selected-route cache material binding must resolve exactly one exact cache entry.', { requirementId, referenceTarget, archiveEntry, count: entryMatches.length }));
200
219
  const entry = entryMatches.length === 1 ? entryMatches[0] : null;
201
220
  const entryBytes = entry ? packageFileBytes({ data: entry.data }) : new Uint8Array();
202
- materialQualifications.push(deepFreeze({ requirementId, classification: String(material.classification || ''), referenceTarget, archiveEntry, bytes: entryBytes.byteLength, sha256: entryBytes.byteLength ? sha256Hex(entryBytes) : '' }));
221
+ const entrySha256 = entryBytes.byteLength ? sha256Hex(entryBytes) : '';
222
+ if (material.bytes !== null && material.bytes !== undefined && Number(material.bytes) !== entryBytes.byteLength) localFindings.push(finding('error', 'portable.handoff-v2-phase1.cache.material-byte-size-mismatch', 'Visible cache material Byte Size must equal the exact owned archive entry bytes.', { requirementId, archiveEntry, declaredBytes: Number(material.bytes), actualBytes: entryBytes.byteLength }));
223
+ if (material.sha256 && String(material.sha256) !== entrySha256) localFindings.push(finding('error', 'portable.handoff-v2-phase1.cache.material-sha256-mismatch', 'Visible cache material SHA256 must equal the exact owned archive entry digest.', { requirementId, archiveEntry }));
224
+ materialQualifications.push(deepFreeze({ requirementId, classification: String(material.classification || ''), referenceTarget, routeWorkspaceId: String(material.routeWorkspaceId || ''), routePath: String(material.routePath || ''), sourceRequirementId: String(material.sourceRequirementId || ''), sourceWorkspaceId: String(material.sourceWorkspaceId || ''), sourcePath: String(material.sourcePath || ''), targetWorkspaceId: String(material.targetWorkspaceId || ''), targetPath: String(material.targetPath || ''), originalPath: String(material.originalPath || ''), archiveEntry, bytes: entryBytes.byteLength, sha256: entrySha256 }));
203
225
  }
204
226
  if (!(cachePayload.parsed?.materials || []).length) localFindings.push(finding('error', 'portable.handoff-v2-phase1.cache.material-bindings-missing', 'Selected-route cache External Payload must visibly bind every owned detached material to one archive entry.'));
205
227
  findings.push(...localFindings);
@@ -20,6 +20,14 @@ export function cacheMaterialBelongsToRoute(material = {}, workspaceId = '', rou
20
20
  return String(material.routeWorkspaceId || '') === String(workspaceId || '') && normalizeRoutePath(material.routePath || '') === normalizeRoutePath(routePath || '');
21
21
  }
22
22
 
23
+ export function cacheMaterialIsBoundedWorkspaceRecovery(material = {}, workspaceId = '') {
24
+ const id = String(workspaceId || '').trim();
25
+ return Boolean(id
26
+ && String(material.classification || '') === 'parent-boundary'
27
+ && String(material.routeWorkspaceId || '') === id
28
+ && String(material.sourceRequirementId || '').startsWith(`bounded-workspace:${id}:`));
29
+ }
30
+
23
31
  export function selectOne(items = [], selector = '', label = 'item', findings = [], selectorFn = (item) => String(item.workspaceId || '')) { const candidates = selector ? items.filter((item) => selectorFn(item) === String(selector)) : items; if (candidates.length !== 1) { findings.push(finding('error', `portable.handoff-v2-phase1.${label}.selection`, `Phase 1 specimen requires exactly one selected ${label}.`, { selector: String(selector || ''), count: candidates.length })); return null; } return candidates[0]; }
24
32
 
25
33
  export function oneFile(files = [], path = '') { const matches = files.filter((file) => String(file.path || '') === String(path || '')); return matches.length === 1 ? matches[0] : null; }
@@ -2,17 +2,22 @@ export function recipientColdProjection(carrier = {}, readPath = '001-1-READ-BEF
2
2
  const routes = Object.freeze((carrier.routes || []).map((route) => Object.freeze({ id: String(route.id || ''), state: String(route.state || ''), workspaceId: String(route.workspaceId || ''), workspaceRelativeHandoffPath: String(route.workspaceRelativePath || ''), packagePath: String(route.packagePath || ''), sha256: String(route.sha256 || ''), from: String(route.parties?.from || ''), to: String(route.parties?.to || '') })));
3
3
  const workspaces = Object.freeze((carrier.workspaces || []).map((workspace) => Object.freeze({ id: String(workspace.id || ''), title: String(workspace.title || workspace.id || ''), slug: String(workspace.slug || ''), qualification: String(workspace.qualification || '') })));
4
4
  const qualified = routes.filter((route) => route.state === 'qualified');
5
- const workspaceMode = String(carrier.mode || '') === 'workspace';
5
+ const mode = String(carrier.mode || '');
6
+ const workspaceMode = mode === 'workspace';
7
+ const bootstrapMode = mode === 'bootstrap';
8
+ const routeMode = !workspaceMode && !bootstrapMode;
6
9
  return deepFreeze({
7
10
  schema: 'tiinex.portable.handoff-cold-consumer-projection.v1', version: 1,
8
11
  status: carrier.status === 'ready' ? 'ready' : 'blocked',
9
12
  controls: Object.freeze({ start: readPath, carrier: 'visible-qualified-artifacts', closure: 'visible-qualified-artifacts-plus-exact-payload-bytes', fileMap: 'not-exposed-in-v2', manifest: 'not-exposed-in-v2' }),
10
- preferredPath: Object.freeze(workspaceMode
11
- ? { ingressKind: 'pointerless-workspace-package', firstSemanticOperation: 'orient-handoff-package', groundingOperation: 'none', qualificationOperation: 'workspace-qualification-after-orientation', minimalHostBootstrapActions: 1, nativeFallback: 'explicit-and-justified-only', providerSpecificSemanticAuthority: false }
12
- : { ingressKind: 'routed-handoff-package', firstSemanticOperation: 'orient-handoff-package', groundingOperation: 'ground-cold-consumer', qualificationOperation: 'qualify-cold-start', minimalHostBootstrapActions: 1, nativeFallback: 'explicit-and-justified-only', providerSpecificSemanticAuthority: false }),
13
+ preferredPath: Object.freeze(bootstrapMode
14
+ ? { ingressKind: 'bootstrap-only-package', firstSemanticOperation: 'orient-handoff-package', groundingOperation: 'none', qualificationOperation: 'bootstrap-qualification-after-orientation', minimalHostBootstrapActions: 1, nativeFallback: 'explicit-and-justified-only', providerSpecificSemanticAuthority: false }
15
+ : workspaceMode
16
+ ? { ingressKind: 'pointerless-workspace-package', firstSemanticOperation: 'orient-handoff-package', groundingOperation: 'none', qualificationOperation: 'workspace-qualification-after-orientation', minimalHostBootstrapActions: 1, nativeFallback: 'explicit-and-justified-only', providerSpecificSemanticAuthority: false }
17
+ : { ingressKind: 'routed-handoff-package', firstSemanticOperation: 'orient-handoff-package', groundingOperation: 'ground-cold-consumer', qualificationOperation: 'qualify-cold-start', minimalHostBootstrapActions: 1, nativeFallback: 'explicit-and-justified-only', providerSpecificSemanticAuthority: false }),
13
18
  workspaces, routes,
14
- selection: Object.freeze({ policy: String(carrier.selection?.policy || ''), qualifiedRouteCount: qualified.length, implicitRouteId: workspaceMode ? '' : qualified.length === 1 ? qualified[0].id : '' }),
15
- authority: Object.freeze({ semanticAuthority: 'none', packageTruthRequired: true, routeBindingAuthority: workspaceMode ? 'none' : 'qualified-visible-artifact-plus-exact-payload-byte-truth-only' })
19
+ selection: Object.freeze({ policy: String(carrier.selection?.policy || ''), qualifiedRouteCount: qualified.length, implicitRouteId: routeMode && qualified.length === 1 ? qualified[0].id : '' }),
20
+ authority: Object.freeze({ semanticAuthority: 'none', packageTruthRequired: true, routeBindingAuthority: routeMode ? 'qualified-visible-artifact-plus-exact-payload-byte-truth-only' : 'none', recipientAuthority: routeMode ? 'qualified-selected-handoff-to-endpoint-only' : 'none', holderAuthority: 'none', workAuthority: 'none' })
16
21
  });
17
22
  }
18
23
 
@@ -26,3 +26,15 @@ export function recipientV2WorkspaceEntryCurrentRead() {
26
26
  Object.freeze({ label: 'Fallback', value: 'fail closed when Tooling cannot qualify the package role, exact Workspace bindings, bootstrap, or all-none Route Discovery profile; native package archaeology is not a preferred-path substitute' })
27
27
  ]);
28
28
  }
29
+
30
+ export function recipientV2BootstrapEntryCurrentRead() {
31
+ return Object.freeze([
32
+ Object.freeze({ label: 'Bootstrap Exception', value: 'before Tiinex Tooling is available, use host capabilities only to read this entry/bootstrap node and extract the declared bootstrap ZIP into a writable runtime location; do not perform package archaeology' }),
33
+ Object.freeze({ label: 'Tooling Entrypoint', value: '`runtime/tools/tiinex-portable.mjs` relative to the chosen bootstrap extraction root; use this exact path and do not search/list the extracted runtime to discover it' }),
34
+ Object.freeze({ label: 'First Tooling Invocation', value: '`node <bootstrap-root>/runtime/tools/tiinex-portable.mjs orient-handoff-package <untouched-bootstrap-package.zip>`' }),
35
+ Object.freeze({ label: 'Carrier Mode', value: 'bootstrap-only carrier; no Workspace/material binding, Handoff route, recipient, Role holder, current work, transfer, acceptance, completion, participation, or grounded-to-act authority is declared or inferred' }),
36
+ Object.freeze({ label: 'Route Selection', value: 'none; the canonical package role requires both source-material binding sections and all route-specific projection fields to declare absence' }),
37
+ Object.freeze({ label: 'Next Action', value: 'after Tooling orientation, continue only with separately supplied qualified semantic material or an explicit later Handoff; this carrier itself supplies no project/source authority' }),
38
+ Object.freeze({ label: 'Fallback', value: 'fail closed when Tooling cannot qualify the package role, Start, bootstrap, or zero-material/zero-route profile; native package archaeology is not a preferred-path substitute' })
39
+ ]);
40
+ }
@@ -2,6 +2,21 @@ import { projectHandoffCarrierOutputFromPackage, projectHandoffHumanOutput } fro
2
2
  import { inspectRecipientFacingV2Topology } from './recipientV2.inspect.js';
3
3
  import { RECIPIENT_V2_READ_PATH } from './recipientV2.topology.js';
4
4
 
5
+
6
+ export function recipientV2GenericInvocation(inspection = {}) {
7
+ const startPath = String(inspection.packageContract?.startPath || RECIPIENT_V2_READ_PATH);
8
+ if (!startPath || inspection.status !== 'valid') return '';
9
+ return [
10
+ 'Tiinex package attached.',
11
+ '',
12
+ 'Cold start: read Start directly; do not list or extract this package.',
13
+ '',
14
+ 'Start:',
15
+ startPath,
16
+ ''
17
+ ].join('\n');
18
+ }
19
+
5
20
  export function recipientV2StandardInvocation(humanOutput = {}, inspection = {}) {
6
21
  const route = humanOutput.selectedRoute || {};
7
22
  return recipientV2InvocationForRoute(route, inspection);
@@ -16,7 +31,7 @@ function recipientV2InvocationForRoute(route = {}, inspection = {}) {
16
31
  'Cold start: read Start directly; do not list or extract this package.',
17
32
  '',
18
33
  'Start:',
19
- RECIPIENT_V2_READ_PATH,
34
+ String(inspection.packageContract?.startPath || RECIPIENT_V2_READ_PATH),
20
35
  'Continue from (do not read native; pass to Tiinex after bootstrap):',
21
36
  String(routeMeta.pointerPath),
22
37
  ''
@@ -25,8 +40,9 @@ function recipientV2InvocationForRoute(route = {}, inspection = {}) {
25
40
 
26
41
  export function projectRecipientV2HumanOutput(humanOutput = {}, inspection = {}) {
27
42
  if (humanOutput.status !== 'ready' && humanOutput.status !== 'selection-required') return humanOutput;
28
- const invocation = humanOutput.primary ? recipientV2StandardInvocation(humanOutput, inspection) : '';
29
- if (humanOutput.primary && !invocation) return Object.freeze({ ...humanOutput, status: 'blocked', findings: Object.freeze([...(humanOutput.findings || []), Object.freeze({ severity: 'error', code: 'portable.handoff-v2-human-output.route-pointer.unresolved', message: 'Recipient-v2 human output requires one exact package-local Handoff route pointer for the selected semantic Handoff.' })]) });
43
+ const routed = String(inspection.packageContract?.packageRole || '') === 'recipient-facing-handoff-carrier';
44
+ const invocation = humanOutput.primary ? (routed ? recipientV2StandardInvocation(humanOutput, inspection) : recipientV2GenericInvocation(inspection)) : '';
45
+ if (humanOutput.primary && !invocation) return Object.freeze({ ...humanOutput, status: 'blocked', findings: Object.freeze([...(humanOutput.findings || []), Object.freeze({ severity: 'error', code: routed ? 'portable.handoff-v2-human-output.route-pointer.unresolved' : 'portable.handoff-v2-human-output.start.unresolved', message: routed ? 'Recipient-v2 human output requires one exact package-local Handoff route pointer for the selected semantic Handoff.' : 'Recipient-v2 generic human output requires one exact qualified package-local Start artifact.' })]) });
30
46
  const sharedRoutes = (humanOutput.sharedRouting?.routes || []).map((item) => {
31
47
  const route = (inspection.carrierProjection?.routes || []).find((candidate) => String(candidate.id || '') === String(item.routeId || ''))
32
48
  || { id: item.routeId, workspaceId: item.workspaceId, workspaceRelativePath: item.workspaceRelativeHandoffPath };
@@ -35,12 +51,15 @@ export function projectRecipientV2HumanOutput(humanOutput = {}, inspection = {})
35
51
  });
36
52
  if (humanOutput.sharedRouting && sharedRoutes.some((item) => !item)) return Object.freeze({ ...humanOutput, status: 'blocked', findings: Object.freeze([...(humanOutput.findings || []), Object.freeze({ severity: 'error', code: 'portable.handoff-v2-human-output.shared-route-pointer.unresolved', message: 'Recipient-v2 shared human output requires one exact package-local Handoff route pointer for every qualified route.' })]) });
37
53
  const sharedRouting = humanOutput.sharedRouting ? Object.freeze({ ...humanOutput.sharedRouting, routes: Object.freeze(sharedRoutes) }) : null;
54
+ const recipientLabel = routed ? String(humanOutput.selectedRoute?.parties?.to || '').trim() : '';
55
+ const presentation = Object.freeze({ ...(humanOutput.presentation || {}), recipientLabel, recipientProjectionAuthority: routed ? 'qualified-selected-handoff-to-endpoint-only' : 'none' });
38
56
  return Object.freeze({
39
57
  ...humanOutput,
58
+ presentation,
40
59
  normalInlineRouting: humanOutput.normalInlineRouting ? Object.freeze({ ...humanOutput.normalInlineRouting, content: invocation }) : null,
41
60
  sharedRouting,
42
61
  fallbackTransportText: humanOutput.fallbackTransportText ? Object.freeze({ ...humanOutput.fallbackTransportText, content: invocation }) : null,
43
- boundary: `${String(humanOutput.boundary || '')} Recipient-v2 host-layer routing is a deterministic address label only: the fixed package Start artifact and exact package-local Continue-from Handoff Route Pointers. Workspace, semantic Handoff path, Role, and Task stay package-owned; shared route texts are read-only addresses and sibling routes remain unselected until one exact qualified route is chosen.`
62
+ boundary: `${String(humanOutput.boundary || '')} Recipient-v2 host-layer transport text is a deterministic projection only: every qualified package may expose its exact Start address; Continue-from and recipient-specific projection exist only for an exact qualified selected Handoff route. Workspace/material/Role presence creates no recipient, holder, participation, delegation, or work authority.`
44
63
  });
45
64
  }
46
65
 
@@ -48,11 +67,34 @@ export function projectPortableHandoffCarrierOutputFromPackage(input = {}) {
48
67
  const bundle = input.bundle || input;
49
68
  const inspection = inspectRecipientFacingV2Topology(bundle);
50
69
  if (inspection.detected !== true) return projectHandoffCarrierOutputFromPackage(input);
51
- const humanOutput = projectRecipientV2HumanOutput(projectHandoffHumanOutput({ projection: inspection.carrierProjection || {}, route: input.route || input.routePath || input.routeId || '', collisionInstance: input.collisionInstance || input.instance || 1 }), inspection);
70
+ const routed = String(inspection.packageContract?.packageRole || '') === 'recipient-facing-handoff-carrier';
71
+ const baseHumanOutput = routed
72
+ ? projectHandoffHumanOutput({ projection: inspection.carrierProjection || {}, route: input.route || input.routePath || input.routeId || '', collisionInstance: input.collisionInstance || input.instance || 1 })
73
+ : genericPackageHumanOutput(inspection, input);
74
+ const humanOutput = projectRecipientV2HumanOutput(baseHumanOutput, inspection);
52
75
  const findings = Object.freeze([...(inspection.findings || []), ...(humanOutput.findings || [])]);
53
76
  const status = inspection.status === 'valid' && humanOutput.status === 'ready' ? 'ready' : humanOutput.status === 'selection-required' ? 'selection-required' : 'blocked';
54
77
  const carrierInspection = boundedRecipientV2Inspection(inspection);
55
- return deepFreeze({ schema: 'tiinex.portable.handoff-carrier-output-projection.v1', status, carrierInspection, humanOutput, findings, boundary: 'Read-only regeneration of recipient-v2 carrier filename and minimal Start/Continue-from transport address from package-qualified route truth. Package byte/provider internals are intentionally omitted from the human-output projection.' });
78
+ return deepFreeze({ schema: 'tiinex.portable.handoff-carrier-output-projection.v1', status, carrierInspection, humanOutput, findings, boundary: 'Read-only regeneration of recipient-v2 carrier filename and minimal Start/Continue-from transport address from package-qualified role and route truth. Route-less package roles expose generic Start transport only; package byte/provider internals are intentionally omitted from the human-output projection.' });
79
+ }
80
+
81
+ function genericPackageHumanOutput(inspection = {}, input = {}) {
82
+ const projection = inspection.carrierProjection || {};
83
+ const mode = String(projection.mode || '');
84
+ const ready = inspection.status === 'valid' && projection.status === 'ready' && ['workspace', 'bootstrap'].includes(mode) && (projection.routes || []).length === 0;
85
+ const dimension = String(projection.lineage?.dimension || '001');
86
+ const filename = String(input.filename || input.projectedFilename || `tiinex-${dimension}.handoff-package.zip`);
87
+ return Object.freeze({
88
+ schema: 'tiinex.portable.handoff-human-output.v1', status: ready ? 'ready' : 'blocked',
89
+ primary: ready ? Object.freeze({ kind: `${mode}-package`, filename, dimension, parentDimension: String(projection.lineage?.parentDimension || ''), checkpointKind: String(projection.lineage?.checkpointKind || ''), routeId: '', workspaceId: '', workspaceRelativeHandoffPath: '', collisionInstance: 1, singleHumanTransportChoice: true }) : null,
90
+ normalInlineRouting: ready ? Object.freeze({ kind: 'transport-text', content: '', normalEmission: true, requiredForHumanCompletion: true, placement: 'adjacent-to-primary', authority: 'none' }) : null,
91
+ sharedRouting: null,
92
+ presentation: Object.freeze({ kind: mode === 'bootstrap' ? 'bootstrap-only-carrier' : 'pointerless-workspace-carrier', label: mode === 'bootstrap' ? 'Bootstrap carrier' : 'Workspace carrier', authority: 'none', recipientLabel: '', recipientProjectionAuthority: 'none' }),
93
+ normalEmissionBoundary: Object.freeze({ allowed: Object.freeze(['package-file', 'generic-start-transport-text']), forbidden: Object.freeze(['route-specific-continue-from', 'recipient-label-from-material', 'holder-label', 'current-work-label']) }),
94
+ fallbackTransportText: ready ? Object.freeze({ supported: true, filename: filename.replace(/\.handoff-package\.zip$/i, '.transport.txt'), content: '', normalEmission: false, requiredForHumanCompletion: false, authority: 'none' }) : null,
95
+ selectedRoute: null, findings: Object.freeze([]),
96
+ boundary: 'Route-less recipient-v2 package output projection. Generic Start transport only; material carriage creates no recipient, holder, participation, delegation, or work authority.'
97
+ });
56
98
  }
57
99
 
58
100