@tiinex/core 0.18.0 → 0.20.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 (38) hide show
  1. package/package.json +8 -6
  2. package/src/schemas/creation.contracts.js +1 -1
  3. package/src/schemas/party/role/tiinex.party.role.v1.schema.js +5 -0
  4. package/src/schemas/party/role/tiinex.party.role.v1.schema.json +2 -2
  5. package/src/schemas/party/role/tiinex.party.role.v1.schema.md +313 -0
  6. package/src/schemas/party/role/tiinex.party.role.v1.schema.runtime.json +18 -6
  7. package/src/schemas/party/role/tiinex.party.role.v1.validate.js +36 -0
  8. package/src/schemas/schema.reference.js +29 -0
  9. package/src/tooling/portable/adapters/cli/cli.command-input.js +8 -1
  10. package/src/tooling/portable/adapters/cli/cli.common-author.js +117 -6
  11. package/src/tooling/portable/adapters/cli/cli.common-output.js +26 -0
  12. package/src/tooling/portable/adapters/cli/cli.handoff-manufacture.js +33 -17
  13. package/src/tooling/portable/adapters/cli/cli.handoff-sibling-allocation.js +139 -2
  14. package/src/tooling/portable/adapters/cli/cli.help.js +1 -1
  15. package/src/tooling/portable/adapters/node/handoff.manufacture.js +30 -5
  16. package/src/tooling/portable/adapters/node/handoff.manufacture.packageParent.js +123 -0
  17. package/src/tooling/portable/audit/audit.capability.js +10 -4
  18. package/src/tooling/portable/grounding/grounding.capsule.js +33 -2
  19. package/src/tooling/portable/grounding/grounding.delegationArtifactAuthority.js +220 -0
  20. package/src/tooling/portable/grounding/grounding.delegationReadiness.js +241 -0
  21. package/src/tooling/portable/grounding/grounding.holderAssignmentModes.js +125 -0
  22. package/src/tooling/portable/grounding/grounding.holderBindingAuthorization.js +80 -0
  23. package/src/tooling/portable/grounding/grounding.implementationSourceAuthority.js +109 -0
  24. package/src/tooling/portable/grounding/grounding.participantAuthority.js +1 -1
  25. package/src/tooling/portable/grounding/grounding.readiness.authority.js +15 -0
  26. package/src/tooling/portable/grounding/grounding.readiness.js +86 -13
  27. package/src/tooling/portable/grounding/grounding.readiness.support.js +2 -2
  28. package/src/tooling/portable/handoff/carrierProjection.routeQualification.js +20 -4
  29. package/src/tooling/portable/handoff/coldStartQualification.grounding.js +116 -5
  30. package/src/tooling/portable/handoff/coldStartQualification.materials.js +92 -10
  31. package/src/tooling/portable/handoff/delegationReturnReservation.js +4 -2
  32. package/src/tooling/portable/handoff/manufacture.js +1 -0
  33. package/src/tooling/portable/handoff/recipientV2.endpointRolePointers.js +1 -1
  34. package/src/tooling/portable/handoff/recipientV2.inspect.projection.js +7 -1
  35. package/src/tooling/portable/handoff/recipientV2.packageV1.build.js +2 -2
  36. package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.js +1 -1
  37. package/src/tooling/portable/handoff/recipientV2.topology.js +5 -3
  38. package/src/tooling/portable/handoff/recipientV2.topology.materials.js +0 -0
@@ -3,6 +3,7 @@ import { packageFileBytes, sha256Hex } from '../../../export/package.bytes.js';
3
3
  import { portableFinding } from '../findings.js';
4
4
  import { inspectStoredWorkspaceArchive } from './workspaceByteProvider.js';
5
5
  import { recipientV2FactsIndex } from './recipientV2.transportManifest.js';
6
+ import { deriveRecipientV2ArtifactFirstPhase1Facts } from './recipientV2.artifactFirst.materials.js';
6
7
  import { parseNamedDeclarationSection } from '../schema/named.declarations.js';
7
8
  import { resolveColdStartRolePointerMaterial } from './coldStartRolePointers.js';
8
9
  import {
@@ -58,7 +59,12 @@ export function collectPackageRoleMaterials(bundle = {}) {
58
59
  }
59
60
 
60
61
  export function resolveReferencedRoleMaterial(bundle = {}, handoff = {}, orientation = null, selectedRoute = null, findings = [], context = null) {
61
- const reference = String(handoff.toReference || '').trim();
62
+ return resolveReferencedEndpointRoleMaterial(bundle, handoff, orientation, selectedRoute, findings, context, 'to');
63
+ }
64
+
65
+ export function resolveReferencedEndpointRoleMaterial(bundle = {}, handoff = {}, orientation = null, selectedRoute = null, findings = [], context = null, endpointParty = 'to') {
66
+ const party = String(endpointParty || 'to').trim().toLowerCase() === 'from' ? 'from' : 'to';
67
+ const reference = String(party === 'from' ? handoff.fromReference : handoff.toReference || '').trim();
62
68
  if (!reference) return null;
63
69
  if (!isExternalReference(reference)) {
64
70
  const workspaceZip = findFile(bundle, String(handoff.packagePath || ''));
@@ -71,7 +77,7 @@ export function resolveReferencedRoleMaterial(bundle = {}, handoff = {}, orienta
71
77
  const markdown = decodeUtf8(matches[0].data || new Uint8Array());
72
78
  if (markdown) return Object.freeze({ path: `${workspaceZip.path}::${resolvedPath}`, markdown, explicit: false, exactReference: reference });
73
79
  }
74
- if (matches.length > 1) findings.push(portableFinding('error', 'portable.cold-start.role.reference-material.ambiguous', 'Handoff To Reference resolves to multiple entries in the selected Workspace archive.', { reference, resolvedPath }));
80
+ if (matches.length > 1) findings.push(portableFinding('error', 'portable.cold-start.role.reference-material.ambiguous', 'Handoff endpoint Reference resolves to multiple entries in the selected Workspace archive.', { reference, resolvedPath }));
75
81
  }
76
82
  }
77
83
  }
@@ -85,14 +91,14 @@ export function resolveReferencedRoleMaterial(bundle = {}, handoff = {}, orienta
85
91
  : projectedFacts
86
92
  ? { role: 'endpoint-role', ...projectedFacts }
87
93
  : {};
88
- if (facts.role !== 'endpoint-role' || String(facts.endpointParty || '').toLowerCase() !== 'to') continue;
94
+ if (facts.role !== 'endpoint-role' || String(facts.endpointParty || '').toLowerCase() !== party) continue;
89
95
  if (facts.referenceTarget && String(facts.referenceTarget) !== reference) continue;
90
96
  const material = resolveColdStartRolePointerMaterial(bundle, facts, findings, String(pointerPath || ''), 'endpoint-role');
91
97
  if (material) endpointPointerMatches.push(Object.freeze({ ...material, exactReference: reference }));
92
98
  }
93
99
  if (endpointPointerMatches.length === 1) return endpointPointerMatches[0];
94
100
  if (endpointPointerMatches.length > 1) {
95
- findings.push(portableFinding('error', 'portable.cold-start.role.reference-material.ambiguous', 'Handoff To Reference resolves through multiple qualified endpoint Role Pointers.', { reference, count: endpointPointerMatches.length }));
101
+ findings.push(portableFinding('error', 'portable.cold-start.role.reference-material.ambiguous', 'Handoff endpoint Reference resolves through multiple qualified endpoint Role Pointers.', { reference, count: endpointPointerMatches.length }));
96
102
  return null;
97
103
  }
98
104
  const cacheMatches = [];
@@ -114,7 +120,7 @@ export function resolveReferencedRoleMaterial(bundle = {}, handoff = {}, orienta
114
120
  }
115
121
  }
116
122
  if (cacheMatches.length === 1) return cacheMatches[0];
117
- if (cacheMatches.length > 1) findings.push(portableFinding('error', 'portable.cold-start.role.reference-material.ambiguous', 'Handoff To Reference resolves to multiple cached exact byte carriers.', { reference, count: cacheMatches.length }));
123
+ if (cacheMatches.length > 1) findings.push(portableFinding('error', 'portable.cold-start.role.reference-material.ambiguous', 'Handoff endpoint Reference resolves to multiple cached exact byte carriers.', { reference, count: cacheMatches.length }));
118
124
  return null;
119
125
  }
120
126
 
@@ -160,6 +166,7 @@ export function parseRoleMaterial(entry) {
160
166
  const roleSection = sectionText(parsed.body?.text || '', 'Role Identity');
161
167
  const boundarySection = sectionText(parsed.body?.text || '', 'Role Boundary');
162
168
  const authoritySection = sectionText(parsed.body?.text || '', 'Authority And Responsibility Boundary');
169
+ const holderSection = sectionText(parsed.body?.text || '', 'Holder Relationship');
163
170
  const limitsSection = sectionText(parsed.body?.text || '', 'Interpretation Limits');
164
171
  const label = sectionField(roleSection, 'Role Label');
165
172
  return deepFreeze({
@@ -171,7 +178,21 @@ export function parseRoleMaterial(entry) {
171
178
  label,
172
179
  roleKind: sectionField(roleSection, 'Role Kind'),
173
180
  boundary: Object.freeze({ inScope: sectionField(boundarySection, 'In Scope'), outOfScope: sectionField(boundarySection, 'Out Of Scope'), context: sectionField(boundarySection, 'Context') }),
174
- authorityBoundary: Object.freeze({ mayDo: sectionField(authoritySection, 'May Do'), doesNotAuthorize: sectionField(authoritySection, 'Does Not Authorize'), reviewBoundary: sectionField(authoritySection, 'Review Boundary') }),
181
+ authorityBoundary: Object.freeze({
182
+ mayDo: sectionField(authoritySection, 'May Do'),
183
+ doesNotAuthorize: sectionField(authoritySection, 'Does Not Authorize'),
184
+ requiredInstrument: sectionField(authoritySection, 'Required Instrument'),
185
+ delegation: sectionField(authoritySection, 'Delegation'),
186
+ reviewBoundary: sectionField(authoritySection, 'Review Boundary')
187
+ }),
188
+ holderRelationship: Object.freeze({
189
+ holderState: sectionField(holderSection, 'Holder State'),
190
+ assignmentModes: sectionField(holderSection, 'Assignment Modes'),
191
+ currentHolder: sectionField(holderSection, 'Current Holder'),
192
+ possibleHolder: sectionField(holderSection, 'Possible Holder'),
193
+ unknownHolder: sectionField(holderSection, 'Unknown Holder'),
194
+ relationArtifact: sectionReferenceTarget(holderSection, 'Relation Artifact')
195
+ }),
175
196
  interpretationLimits: Object.freeze({ doesNotProve: sectionField(limitsSection, 'Does Not Prove'), mustNotBeTreatedAs: sectionField(limitsSection, 'Must Not Be Treated As') }),
176
197
  parentTrace: String(parsed.envelope?.parent?.trace || ''),
177
198
  parentSchemaId: String(parsed.envelope?.parent?.schema?.id || '')
@@ -254,7 +275,7 @@ function hydrateRequiredContextEntry(bundle = {}, entry = {}, context = null) {
254
275
  })
255
276
  };
256
277
  if (base.state !== 'qualified') return Object.freeze({ ...base, contentState: 'unavailable', content: '' });
257
- const hydrated = resolveQualifiedMaterialBytes(bundle, resolution, context);
278
+ const hydrated = resolveQualifiedMaterialBytes(bundle, resolution, context, base);
258
279
  if (!hydrated.bytes) return Object.freeze({ ...base, state: 'unresolved', contentState: 'unavailable', content: '' });
259
280
  const actualSha256 = sha256Hex(hydrated.bytes);
260
281
  const identityQualified = (!base.bytes || hydrated.bytes.byteLength === base.bytes) && (!base.sha256 || actualSha256 === base.sha256);
@@ -262,14 +283,21 @@ function hydrateRequiredContextEntry(bundle = {}, entry = {}, context = null) {
262
283
  return Object.freeze({
263
284
  ...base,
264
285
  state: identityQualified ? 'qualified' : 'identity-mismatch',
286
+ workspaceId: String(hydrated.workspaceId || base.workspaceId || ''),
287
+ archivePackagePath: String(hydrated.archivePackagePath || base.archivePackagePath || ''),
288
+ innerPath: String(hydrated.innerPath || base.innerPath || ''),
289
+ packagePath: String(hydrated.packagePath || base.packagePath || ''),
290
+ providerMode: String(hydrated.providerMode || base.providerMode || ''),
291
+ kind: String(hydrated.kind || base.kind || ''),
265
292
  actualBytes: hydrated.bytes.byteLength,
266
293
  actualSha256,
294
+ provenance: Object.freeze({ ...(base.provenance || {}), providerMode: String(hydrated.providerMode || base.providerMode || ''), resolutionKind: String(hydrated.kind || base.kind || '') }),
267
295
  contentState: text ? 'hydrated-text' : identityQualified ? 'qualified-locator-only' : 'unavailable',
268
296
  content: text
269
297
  });
270
298
  }
271
299
 
272
- function resolveQualifiedMaterialBytes(bundle = {}, resolution = {}, context = null) {
300
+ function resolveQualifiedMaterialBytes(bundle = {}, resolution = {}, context = null, requirement = {}) {
273
301
  const kind = String(resolution.kind || '');
274
302
  if (kind === 'workspace-archive-entry' || kind === 'workspace-cache-entry') {
275
303
  const archivePath = String(resolution.archivePackagePath || resolution.packagePath || '');
@@ -285,17 +313,64 @@ function resolveQualifiedMaterialBytes(bundle = {}, resolution = {}, context = n
285
313
  }
286
314
  const packagePath = String(resolution.packagePath || '');
287
315
  const file = packagePath ? findFile(bundle, packagePath) : null;
288
- return Object.freeze({ bytes: file ? packageFileBytes(file) : null });
316
+ if (file) return Object.freeze({ bytes: packageFileBytes(file), packagePath });
317
+ if (kind === 'materialized-required-material') {
318
+ const requirementId = String(requirement.requirementId || '');
319
+ const referenceTarget = String(requirement.referenceTarget || '');
320
+ const matches = [];
321
+ const factsIndex = recipientFactsIndexForColdStart(bundle, context).map;
322
+ const visibleFactsIndex = deriveRecipientV2ArtifactFirstPhase1Facts(bundle.files || []);
323
+ for (const candidateFile of bundle.files || []) {
324
+ const candidatePath = String(candidateFile.path || '');
325
+ const facts = factsIndex.get(candidatePath) || visibleFactsIndex.get(candidatePath) || null;
326
+ if (facts?.role !== 'workspace-dependency-cache' && !/dependency cache/i.test(String(facts?.role || ''))) continue;
327
+ for (const material of facts.materials || []) {
328
+ if (requirementId && String(material.requirementId || '') !== requirementId) continue;
329
+ if (referenceTarget && String(material.referenceTarget || '') !== referenceTarget) continue;
330
+ const archivePath = String(facts.archivePath || '');
331
+ const archiveFile = findFile(bundle, archivePath);
332
+ if (!archiveFile) continue;
333
+ const archive = inspectWorkspaceArchiveForColdStart(archiveFile, context);
334
+ if (archive.state !== 'qualified') continue;
335
+ const entries = (archive.entries || []).filter((entry) => String(entry.path || '') === String(material.archiveEntry || ''));
336
+ if (entries.length !== 1) continue;
337
+ const entry = entries[0];
338
+ const data = packageFileBytes({ data: entry.data });
339
+ const actualSha = sha256Hex(data);
340
+ if (Number(material.bytes || 0) && Number(material.bytes || 0) !== data.byteLength) continue;
341
+ if (String(material.sha256 || '') && String(material.sha256 || '') !== actualSha) continue;
342
+ if (Number(resolution.bytes || 0) && Number(resolution.bytes || 0) !== data.byteLength) continue;
343
+ if (String(resolution.sha256 || '') && String(resolution.sha256 || '') !== actualSha) continue;
344
+ matches.push(Object.freeze({
345
+ bytes: data, providerMode: 'cache', kind: 'workspace-cache-entry',
346
+ workspaceId: String(material.targetWorkspaceId || material.sourceWorkspaceId || facts.workspaceId || ''),
347
+ innerPath: String(material.targetPath || material.originalPath || ''),
348
+ archivePackagePath: archivePath, packagePath: archivePath
349
+ }));
350
+ }
351
+ }
352
+ if (matches.length === 1) return matches[0];
353
+ }
354
+ return Object.freeze({ bytes: null });
289
355
  }
290
356
 
291
357
  export function parseHandoffGrounding(markdown, route) {
292
358
  const parties = sectionText(markdown, 'Handoff Parties');
293
359
  const transferSection = parseNamedDeclarationSection(markdown, '## Transfers');
360
+ const retainedSection = parseNamedDeclarationSection(markdown, '## Retained Responsibilities');
294
361
  const completion = sectionText(markdown, 'Completion Expectation');
295
362
  const transfers = Object.freeze((transferSection.entries || []).filter((entry) => String(entry.name || '').trim().toLowerCase() !== 'none').map((entry) => Object.freeze({
296
363
  id: String(entry.name || ''),
297
364
  transferKind: String(entry.fields?.['Transfer Kind'] || ''),
298
365
  description: String(entry.fields?.Description || ''),
366
+ controllingArtifact: String(entry.fields?.['Controlling Artifact'] || ''),
367
+ controllingArtifactTarget: markdownReferenceTarget(entry.fields?.['Controlling Artifact'] || ''),
368
+ boundary: String(entry.fields?.Boundary || '')
369
+ })));
370
+ const retainedResponsibilities = Object.freeze((retainedSection.entries || []).filter((entry) => String(entry.name || '').trim().toLowerCase() !== 'none').map((entry) => Object.freeze({
371
+ id: String(entry.name || ''),
372
+ retainedBy: String(entry.fields?.['Retained By'] || ''),
373
+ responsibility: String(entry.fields?.Responsibility || ''),
299
374
  boundary: String(entry.fields?.Boundary || '')
300
375
  })));
301
376
  return deepFreeze({
@@ -308,6 +383,7 @@ export function parseHandoffGrounding(markdown, route) {
308
383
  fromReference: sectionReferenceTarget(parties, 'From Reference'),
309
384
  toReference: sectionReferenceTarget(parties, 'To Reference'),
310
385
  transfers,
386
+ retainedResponsibilities,
311
387
  completionExpectation: Object.freeze({
312
388
  signalKind: sectionField(completion, 'Signal Kind'),
313
389
  signalMeaning: sectionField(completion, 'Signal Meaning'),
@@ -322,8 +398,14 @@ export function parseHandoffGrounding(markdown, route) {
322
398
  });
323
399
  }
324
400
 
401
+ function markdownReferenceTarget(value = '') {
402
+ const text = String(value || '').trim();
403
+ const match = text.match(/\[[^\]]*\]\(([^)]+)\)/);
404
+ return match ? String(match[1] || '').trim() : '';
405
+ }
406
+
325
407
  export function emptyHandoffGrounding() {
326
- return deepFreeze({ schemaId: '', purpose: '', from: '', fromKind: '', fromReference: '', to: '', toKind: '', toReference: '', transfers: Object.freeze([]), completionExpectation: Object.freeze({ signalKind: '', signalMeaning: '', returnTo: '' }), routeId: '', workspaceId: '', workspaceRelativePath: '', packagePath: '', sha256: '', boundary: 'No Handoff material supplied.' });
408
+ return deepFreeze({ schemaId: '', purpose: '', from: '', fromKind: '', fromReference: '', to: '', toKind: '', toReference: '', transfers: Object.freeze([]), retainedResponsibilities: Object.freeze([]), completionExpectation: Object.freeze({ signalKind: '', signalMeaning: '', returnTo: '' }), routeId: '', workspaceId: '', workspaceRelativePath: '', packagePath: '', sha256: '', boundary: 'No Handoff material supplied.' });
327
409
  }
328
410
 
329
411
  export function resolveGroundingRouteMarkdown(bundle = {}, selectedRoute = {}, findings = [], context = null) {
@@ -32,14 +32,16 @@ export function qualifyDelegationReturnReservation(input = {}) {
32
32
  });
33
33
  }
34
34
 
35
+ const rawSupplied = rawIndex !== undefined && rawIndex !== null && String(rawIndex).trim() !== '';
35
36
  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
+ if (rawSupplied && siblingIndex === null) findings.push(finding('error', 'portable.delegation-return-reservation.sibling-index.invalid', 'Explicit return package sibling override must be an integer in the supported range 1..9999.'));
37
38
  return freeze({
38
39
  schema: PORTABLE_DELEGATION_RETURN_RESERVATION_SCHEMA_ID,
39
40
  state: findings.length ? 'blocked' : 'qualified',
40
41
  returnExpected: true,
41
42
  carrierKind: 'non-major',
42
43
  siblingIndex,
44
+ allocationMode: siblingIndex === null ? 'derive-from-qualified-recipient-selected-pointer' : 'explicit-advanced-override',
43
45
  findings,
44
46
  boundary: boundary()
45
47
  });
@@ -95,7 +97,7 @@ function field(section = '', name = '') {
95
97
  const match = String(section || '').match(new RegExp(`^\\s*-\\s+${escapeRegExp(name)}\\s*:\\s*(.+?)\\s*$`, 'mi'));
96
98
  return String(match?.[1] || '').trim();
97
99
  }
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.'; }
100
+ function boundary() { return 'Transport preflight only. Ordinary non-Major return allocation is derived later from the exact qualified selected parent Handoff Pointer ordinal; an explicit return sibling index is an advanced compatibility override only. This projection never promotes transport allocation into semantic Parent, Workspace, Role, acceptance, completion, participant, process, or source authority.'; }
99
101
  function finding(severity, code, message) { return Object.freeze({ severity, code, message }); }
100
102
  function escapeRegExp(value = '') { return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
101
103
  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)]))); }
@@ -77,6 +77,7 @@ export function manufactureRecipientRelativeHandoffPackage(input = {}, options =
77
77
  roundtrip: upgraded.roundtrip || null,
78
78
  toolingBootstrap: input.toolingBootstrap || null,
79
79
  manufacturingEvidence: input.manufacturingEvidence || null,
80
+ carrierAllocation: input.carrierAllocation || input.manufacturingEvidence?.carrierAllocation || null,
80
81
  schemaReferencePreflight,
81
82
  returnCarrierReservationPreflight,
82
83
  reconciliationProofQualification,
@@ -136,7 +136,7 @@ export function buildParticipantRolePointerChain(input = {}) {
136
136
  prose: 'This Pointer is recipient discovery/grounding only. It does not declare semantic participation, change Handoff From/To, prove a human holder, or create Role authority; participation meaning remains owned by authoritative Handoff/Relation/context authority.',
137
137
  currentRead: [
138
138
  { label: 'Route Id', value: `\`${String(input.route?.id || '')}\`` },
139
- { label: 'Grounding Requirement Id', value: `\`${String(requirement.id || '')}\`` },
139
+ { label: 'Participant Requirement Id', value: `\`${String(requirement.id || '')}\`` },
140
140
  ...(roleFacts.roleLabelHint ? [{ label: 'Role Label Hint', value: roleFacts.roleLabelHint }] : []),
141
141
  { label: 'Role Reference', value: roleFacts.referenceTarget ? `\`${roleFacts.referenceTarget}\`` : 'exact carried target' },
142
142
  { label: 'Target Carrier Kind', value: target.carrierKind },
@@ -5,6 +5,7 @@ export function projectRecipientV2Routes(routePointers = [], endpointPointers =
5
5
  const qualified = (qualifiedRoutes || []).find((route) => String(route.workspaceId || '') === String(item.facts?.workspaceId || '') && String(route.workspaceRelativePath || '') === String(item.facts?.workspaceRelativeHandoffPath || '')) || null;
6
6
  return Object.freeze({
7
7
  pointerPath: item.path,
8
+ routeId: String(item.facts?.routeId || qualified?.id || ''),
8
9
  workspaceId: String(item.facts?.workspaceId || ''),
9
10
  workspaceRelativeHandoffPath: String(item.facts?.workspaceRelativeHandoffPath || ''),
10
11
  returnCarrierReservation: item.facts?.returnCarrierReservation || qualified?.returnCarrierReservation || null,
@@ -17,6 +18,7 @@ export function projectRecipientV2Routes(routePointers = [], endpointPointers =
17
18
  export function projectRecipientV2EndpointRoles(pointers = []) {
18
19
  return Object.freeze(pointers.map((item) => Object.freeze({
19
20
  pointerPath: item.path,
21
+ routeId: String(item.facts?.routeId || qualified?.id || ''),
20
22
  workspaceId: String(item.facts?.workspaceId || ''),
21
23
  routeId: String(item.facts?.routeId || ''),
22
24
  requirementId: String(item.facts?.endpointRequirementId || ''),
@@ -36,13 +38,17 @@ export function projectRecipientV2EndpointRoles(pointers = []) {
36
38
  export function projectRecipientV2ParticipantRoles(pointers = []) {
37
39
  return Object.freeze(pointers.map((item) => Object.freeze({
38
40
  pointerPath: item.path,
39
- workspaceId: String(item.facts?.workspaceId || ''),
40
41
  routeId: String(item.facts?.routeId || ''),
42
+ workspaceId: String(item.facts?.workspaceId || ''),
43
+ requirementId: String(item.facts?.participantRequirementId || ''),
41
44
  roleLabelHint: String(item.facts?.roleLabelHint || ''),
42
45
  referenceTarget: String(item.facts?.referenceTarget || ''),
43
46
  targetCarrierKind: String(item.facts?.targetCarrierKind || ''),
44
47
  targetWorkspaceId: String(item.facts?.targetWorkspaceId || ''),
48
+ archivePath: String(item.facts?.archivePath || ''),
45
49
  targetInnerPath: String(item.facts?.targetInnerPath || item.facts?.targetArchiveEntry || ''),
50
+ targetArchiveEntry: String(item.facts?.targetArchiveEntry || ''),
51
+ targetBytes: Number(item.facts?.targetBytes || 0),
46
52
  targetSha256: String(item.facts?.targetSha256 || '')
47
53
  })));
48
54
  }
@@ -9,7 +9,7 @@ import { RECIPIENT_V2_ROUTE_SELECTION_AUTHORITY, RECIPIENT_V2_SIBLING_ROUTE_INFE
9
9
  import { recipientV2TransportFacts } from './recipientV2.transportManifest.js';
10
10
  import { buildRecipientV2BootstrapCarrier, buildRecipientV2WorkspaceCarriers, recipientV2ParentAuthority } from './recipientV2.topology.workspaces.js';
11
11
  import { buildEndpointRolePointerChain, buildParticipantRolePointerChain } from './recipientV2.endpointRolePointers.js';
12
- import { bindingForWorkspace, boundedWorkspaceClaimsDetachedRecovery, detachedMaterial, duplicates, finding, roleMaterialTarget, routeClaimsDetachedMaterial, safeToken, uniqueFileIndex } from './recipientV2.topology.materials.js';
12
+ import { bindingForWorkspace, boundedWorkspaceClaimsDetachedRecovery, coalesceDetachedCacheMaterials, detachedMaterial, duplicates, finding, roleMaterialTarget, routeClaimsDetachedMaterial, safeToken, uniqueFileIndex } from './recipientV2.topology.materials.js';
13
13
  import { RECIPIENT_V2_PACKAGE_V1_FORMAT_ID, RECIPIENT_V2_PACKAGE_V1_ROOT_PATH, RECIPIENT_V2_PACKAGE_V1_SCHEMA_ID, RECIPIENT_V2_PACKAGE_V1_SCHEMA_TARGET } from './recipientV2.packageV1.constants.js';
14
14
  import { renderHandoffPackageV1 } from './recipientV2.packageV1.contract.js';
15
15
  import { inspectRecipientFacingV2PackageV1 } from './recipientV2.packageV1.inspect.js';
@@ -136,7 +136,7 @@ function buildRecipientFacingV2PackageV1Prepared(input = {}, sealedByWorkspaceId
136
136
  const workspace = workspaceById.get(plan.workspaceId);
137
137
  if (!workspace) continue;
138
138
  const binding = bindingForWorkspace(descriptor, workspace.workspaceId);
139
- const materials = detached.filter((item) => (workspace.workspaceId === String(route.workspaceId || '') && routeClaimsDetachedMaterial(route, item)) || boundedWorkspaceClaimsDetachedRecovery(binding, item));
139
+ const materials = coalesceDetachedCacheMaterials(detached.filter((item) => (workspace.workspaceId === String(route.workspaceId || '') && routeClaimsDetachedMaterial(route, item)) || boundedWorkspaceClaimsDetachedRecovery(binding, item)));
140
140
  if (!materials.length) continue;
141
141
  const artifactPath = `${plan.prefix}-1-cache.trace.md`;
142
142
  const archivePath = `${plan.prefix}-1-cache.zip`;
@@ -256,7 +256,7 @@ export function inspectRecipientFacingV2PackageV1(bundle = {}, options = {}) {
256
256
  return deepFreeze({
257
257
  schema: 'tiinex.portable.recipient-facing-handoff-package-v1.inspection.v1', detected: Boolean(packageFile), status, format: RECIPIENT_V2_PACKAGE_V1_FORMAT_ID,
258
258
  rootArtifact: packageFile ? Object.freeze({ path: packageFile.path, schemaId: RECIPIENT_V2_PACKAGE_V1_SCHEMA_ID, sha256: sha256Hex(packageFileBytes(packageFile)), carrierLineage: lineage }) : null,
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),
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, workspaceRepresentationArtifactPath: String(item.representationArtifact?.path || ''), workspacePayloadArtifactPath: String(item.payloadArtifact?.path || item.protectedPayloadArtifact?.path || ''), workspaceArchivePath: item.archiveFile?.path || '', sourceWorkspaceTargetInnerPath: item.facts.sourceWorkspaceTargetInnerPath, sourceWorkspaceTargetSha256: item.facts.sourceWorkspaceTargetSha256 }))), sealedWorkspaces: Object.freeze(sealedWorkspaceBindings),
260
260
  routes: projectRecipientV2Routes(routePointers, endpointRolePointers, participantRolePointers, carrierProjection?.routes || []), endpointRoles: projectRecipientV2EndpointRoles(endpointRolePointers), participantRoles: projectRecipientV2ParticipantRoles(participantRolePointers),
261
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 || [] }))),
262
262
  bootstrapInspection, transportManifest: null, artifactFacts: Object.freeze(generatedArtifacts.map((item) => Object.freeze({ path: item.path, facts: item.facts }))), descriptor, workspaceByteProvider, carrierProjection, coldConsumerProjection,
@@ -16,6 +16,8 @@ import { buildEndpointRolePointerChain, buildParticipantRolePointerChain } from
16
16
  import {
17
17
  bindingForWorkspace,
18
18
  boundedWorkspaceClaimsDetachedRecovery,
19
+ coalesceDetachedCacheMaterials,
20
+ detachedCacheRepresentationKey,
19
21
  deepFreeze,
20
22
  detachedMaterial,
21
23
  duplicates,
@@ -144,7 +146,7 @@ function buildRecipientFacingV2TopologyLegacy(input = {}) {
144
146
  if (!workspace) continue;
145
147
  const workspaceRoutes = routePlans.filter((plan) => plan.workspace.workspaceId === workspace.workspaceId);
146
148
  const binding = bindingForWorkspace(descriptor, workspace.workspaceId);
147
- const materials = detached.filter((item) => workspaceRoutes.some((plan) => routeClaimsDetachedMaterial(plan.route, item)) || boundedWorkspaceClaimsDetachedRecovery(binding, item));
149
+ const materials = coalesceDetachedCacheMaterials(detached.filter((item) => workspaceRoutes.some((plan) => routeClaimsDetachedMaterial(plan.route, item)) || boundedWorkspaceClaimsDetachedRecovery(binding, item)));
148
150
  if (!materials.length) continue;
149
151
  const artifactPath = `001-${workspacePlan.ordinal}-1-cache.trace.md`;
150
152
  const archivePath = `001-${workspacePlan.ordinal}-1-cache.zip`;
@@ -166,9 +168,9 @@ function buildRecipientFacingV2TopologyLegacy(input = {}) {
166
168
  }
167
169
 
168
170
  const claimedDetached = new Set();
169
- for (const cache of topology.caches) for (const material of cache.materials || []) claimedDetached.add(`${material.requirementId}\u0000${material.referenceTarget}\u0000${material.sha256}`);
171
+ for (const cache of topology.caches) for (const material of cache.materials || []) claimedDetached.add(detachedCacheRepresentationKey(material));
170
172
  for (const item of detached) {
171
- const key = `${item.requirementId}\u0000${item.referenceTarget}\u0000${item.sha256}`;
173
+ const key = detachedCacheRepresentationKey(item);
172
174
  if (!claimedDetached.has(key)) findings.push(finding('error', 'portable.handoff-v2-surface.cache.material-unowned', 'Detached dependency bytes are not claimed by any Workspace-scoped Handoff route cache.', { requirementId: item.requirementId || '', referenceTarget: item.referenceTarget || '' }));
173
175
  }
174
176