@tiinex/core 0.3.0 → 0.4.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 (41) hide show
  1. package/package.json +8 -6
  2. package/src/public/index.js +21 -0
  3. package/src/public/node.js +1 -0
  4. package/src/release/plan.mjs +18 -5
  5. package/src/release/run.mjs +7 -5
  6. package/src/tooling/portable/adapters/cli/cli.command-input.js +3 -0
  7. package/src/tooling/portable/adapters/cli/cli.common-output.js +23 -0
  8. package/src/tooling/portable/adapters/cli/cli.handoff-manufacture.js +2 -0
  9. package/src/tooling/portable/adapters/cli/cli.help.js +18 -2
  10. package/src/tooling/portable/adapters/cli/cli.material-policy.js +0 -1
  11. package/src/tooling/portable/adapters/cli/cli.operator-bridge.js +0 -13
  12. package/src/tooling/portable/adapters/cli/cli.run.js +1 -1
  13. package/src/tooling/portable/adapters/cli/cli.source-frontier-comparison.js +50 -0
  14. package/src/tooling/portable/adapters/node/handoff.manufacture.js +14 -0
  15. package/src/tooling/portable/adapters/node/handoff.manufacture.packageParent.js +80 -16
  16. package/src/tooling/portable/adapters/node/handoff.manufacture.requirements.js +44 -11
  17. package/src/tooling/portable/adapters/node/handoff.manufacture.scope.js +96 -1
  18. package/src/tooling/portable/adapters/node/sourceFrontierComparison.js +194 -0
  19. package/src/tooling/portable/comparison/sourceFrontierComparison.js +501 -0
  20. package/src/tooling/portable/grounding/grounding.readiness.js +8 -4
  21. package/src/tooling/portable/grounding/grounding.readiness.support.js +63 -1
  22. package/src/tooling/portable/handoff/contextAudit.js +21 -1
  23. package/src/tooling/portable/handoff/materialClosure.descriptor.js +1 -1
  24. package/src/tooling/portable/handoff/materialClosure.materials.js +1 -1
  25. package/src/tooling/portable/handoff/recipientV2.artifacts.js +1 -1
  26. package/src/tooling/portable/handoff/recipientV2.inspect.helpers.js +6 -1
  27. package/src/tooling/portable/handoff/recipientV2.packageV1.build.js +93 -19
  28. package/src/tooling/portable/handoff/recipientV2.packageV1.contract.js +30 -1
  29. package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.helpers.js +8 -2
  30. package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.js +122 -10
  31. package/src/tooling/portable/handoff/recipientV2.packageV1.js +2 -1
  32. package/src/tooling/portable/handoff/recipientV2.packageV1.secure.js +87 -0
  33. package/src/tooling/portable/handoff/recipientV2.packageV1.shared.js +9 -1
  34. package/src/tooling/portable/handoff/recipientV2.topology.js +2 -2
  35. package/src/tooling/portable/handoff/recipientV2.topology.materials.js +9 -1
  36. package/src/tooling/portable/handoff/transportEnvelopeV1.js +76 -0
  37. package/src/tooling/portable/index.js +4 -0
  38. package/src/tooling/portable/operation.catalog.js +8 -0
  39. package/src/tooling/portable/operation.catalog.package.js +0 -8
  40. package/src/transport/secureTransportV1.js +339 -0
  41. package/src/tooling/portable/handoff/sourceFrontierComparison.js +0 -186
@@ -164,7 +164,27 @@ function auditRecipientV2(bundle = {}) {
164
164
  const findings = [...(inspection.findings || [])];
165
165
  if (unexplained) findings.push(finding('error', 'portable.handoff-context.v2.coverage.incomplete', 'Recipient-facing v2 context audit could not classify every visible root carrier.', { total: files.length, classified, unexplained }));
166
166
  const cacheMaterials = (inspection.caches || []).flatMap((cache) => cache.materials || []);
167
- return deepFreeze({ schema: PORTABLE_HANDOFF_CONTEXT_AUDIT_SCHEMA_ID, status: inspection.status === 'valid' && !unexplained ? 'ready' : 'blocked', coverage: Object.freeze({ nonControlCarrierCount: files.length, classifiedCarrierCount: classified, unexplainedCarrierCount: unexplained, state: unexplained ? 'incomplete' : 'qualified' }), workspaceMaterializations: Object.freeze((inspection.workspaces || []).map((item) => Object.freeze({ workspaceId: item.workspaceId, reason: 'complete-workspace-archive-representation', qualification: 'qualified', carrierMode: 'archive', workspaceTargetPackagePath: item.workspaceArtifactPath, archivePackagePath: item.workspaceArchivePath, sourceWorkspaceTargetInnerPath: item.sourceWorkspaceTargetInnerPath, sourceWorkspaceTargetSha256: item.sourceWorkspaceTargetSha256 }))), materialCarriers: Object.freeze([]), generatedEntrypoints: Object.freeze([String(inspection.rootArtifact?.path || ''), RECIPIENT_V2_READ_PATH, ...(inspection.endpointRoles || []).map((item) => item.pointerPath), ...(inspection.participantRoles || []).map((item) => item.pointerPath), ...(inspection.routes || []).map((item) => item.pointerPath)].filter(Boolean)), namedPackageRequirements: Object.freeze([]), explicitDetachedMaterial: Object.freeze(cacheMaterials), unexplainedCarriers: Object.freeze([]), duplicateByteSummary: Object.freeze({ materialCarriersAlsoPresentInWorkspace: 0, totalMaterialCarriers: cacheMaterials.length, interpretation: 'Exact Workspace-scoped recipient cache material is permitted only when not satisfied by a qualified Workspace archive.' }), routeGrounding: Object.freeze((inspection.carrierProjection?.routes || []).map((route) => Object.freeze({ routeId: route.id, workspaceId: route.workspaceId, handoffPackagePath: route.packagePath, required: route.requiredClosure?.requirements || [] }))), inspections: Object.freeze({ recipientV2: inspection.status }), findings: Object.freeze(dedupeFindings(findings)), boundary: 'Recipient-facing v2 carriage audit over qualified visible Tiinex artifacts, exact payload bytes, and one transport-owned manifest control surface.' });
167
+ const parentBoundaryGroundingEligible = inspection.status === 'valid' && String(inspection.schema || '') === 'tiinex.portable.recipient-facing-handoff-package-v1.inspection.v1';
168
+ const lineageMaterializations = parentBoundaryGroundingEligible
169
+ ? (inspection.caches || []).flatMap((cache) => (cache.materials || [])
170
+ .filter((material) => String(material.classification || '') === 'parent-boundary')
171
+ .map((material) => Object.freeze({
172
+ qualification: 'qualified-package-v1-parent-boundary-cache',
173
+ workspaceId: String(material.targetWorkspaceId || ''),
174
+ workspaceRelativePath: String(material.targetPath || material.originalPath || ''),
175
+ sourceWorkspaceId: String(material.sourceWorkspaceId || ''),
176
+ sourceWorkspaceRelativePath: String(material.sourcePath || ''),
177
+ routeWorkspaceId: String(material.routeWorkspaceId || ''),
178
+ routeWorkspaceRelativePath: String(material.routePath || ''),
179
+ requirementId: String(material.requirementId || ''),
180
+ referenceTarget: String(material.referenceTarget || ''),
181
+ archivePackagePath: String(cache.archivePath || ''),
182
+ archiveEntry: String(material.archiveEntry || ''),
183
+ bytes: Number(material.bytes || 0),
184
+ sha256: String(material.sha256 || '')
185
+ })))
186
+ : [];
187
+ return deepFreeze({ schema: PORTABLE_HANDOFF_CONTEXT_AUDIT_SCHEMA_ID, status: inspection.status === 'valid' && !unexplained ? 'ready' : 'blocked', coverage: Object.freeze({ nonControlCarrierCount: files.length, classifiedCarrierCount: classified, unexplainedCarrierCount: unexplained, state: unexplained ? 'incomplete' : 'qualified' }), workspaceMaterializations: Object.freeze((inspection.workspaces || []).map((item) => Object.freeze({ workspaceId: item.workspaceId, reason: 'complete-workspace-archive-representation', qualification: 'qualified', carrierMode: 'archive', workspaceTargetPackagePath: item.workspaceArtifactPath, archivePackagePath: item.workspaceArchivePath, sourceWorkspaceTargetInnerPath: item.sourceWorkspaceTargetInnerPath, sourceWorkspaceTargetSha256: item.sourceWorkspaceTargetSha256 }))), lineageMaterializations: Object.freeze(lineageMaterializations), materialCarriers: Object.freeze([]), generatedEntrypoints: Object.freeze([String(inspection.rootArtifact?.path || ''), RECIPIENT_V2_READ_PATH, ...(inspection.endpointRoles || []).map((item) => item.pointerPath), ...(inspection.participantRoles || []).map((item) => item.pointerPath), ...(inspection.routes || []).map((item) => item.pointerPath)].filter(Boolean)), namedPackageRequirements: Object.freeze([]), explicitDetachedMaterial: Object.freeze(cacheMaterials), unexplainedCarriers: Object.freeze([]), duplicateByteSummary: Object.freeze({ materialCarriersAlsoPresentInWorkspace: 0, totalMaterialCarriers: cacheMaterials.length, interpretation: 'Exact Workspace-scoped recipient cache material is permitted only when not satisfied by a qualified Workspace archive.' }), routeGrounding: Object.freeze((inspection.carrierProjection?.routes || []).map((route) => Object.freeze({ routeId: route.id, workspaceId: route.workspaceId, handoffPackagePath: route.packagePath, required: route.requiredClosure?.requirements || [] }))), inspections: Object.freeze({ recipientV2: inspection.status, parentBoundaryGrounding: parentBoundaryGroundingEligible ? 'qualified-package-v1' : 'not-projected' }), findings: Object.freeze(dedupeFindings(findings)), boundary: 'Recipient-facing v2 carriage audit over qualified visible Tiinex artifacts and exact payload bytes. Complete Workspace snapshots and independently qualified package-v1 detached Parent-boundary lineage are projected separately; detached lineage never implies whole-Workspace carriage or membership.' });
168
188
  }
169
189
 
170
190
  function indexWorkspaceEntries(workspaces = []) {
@@ -131,7 +131,7 @@ function resolvePackagedWorkspace(index, key = '') {
131
131
 
132
132
 
133
133
  function descriptorRequirement(item = {}) {
134
- return Object.freeze({ requirementId: String(item.requirementId || ''), name: String(item.requirementName || item.name || ''), classification: String(item.classification || ''), disposition: String(item.disposition || ''), referenceTarget: String(item.referenceTarget || ''), reason: String(item.reason || ''), recipientReferenceCapability: Boolean(item.recipientReferenceCapability), routeWorkspaceId: String(item.routeWorkspaceId || ''), routePath: String(item.routePath || ''), sourceRequirementId: String(item.sourceRequirementId || ''), materializedPackagePath: String(item.packagePath || item.selectedMaterial?.packagePath || ''), selectedMaterial: descriptorSelectedMaterial(item.selectedMaterial) });
134
+ return Object.freeze({ requirementId: String(item.requirementId || ''), name: String(item.requirementName || item.name || ''), classification: String(item.classification || ''), disposition: String(item.disposition || ''), referenceTarget: String(item.referenceTarget || ''), reason: String(item.reason || ''), recipientReferenceCapability: Boolean(item.recipientReferenceCapability), routeWorkspaceId: String(item.routeWorkspaceId || ''), routePath: String(item.routePath || ''), sourceRequirementId: String(item.sourceRequirementId || ''), sourceWorkspaceId: String(item.sourceWorkspaceId || ''), sourcePath: String(item.sourcePath || ''), targetWorkspaceId: String(item.targetWorkspaceId || ''), targetPath: String(item.targetPath || ''), materializedPackagePath: String(item.packagePath || item.selectedMaterial?.packagePath || ''), selectedMaterial: descriptorSelectedMaterial(item.selectedMaterial) });
135
135
  }
136
136
  function descriptorSelectedMaterial(material = null) {
137
137
  if (!material) return null;
@@ -110,7 +110,7 @@ function candidateBoundToRequirement(candidate, requirement) {
110
110
  return candidate.referenceTarget === target;
111
111
  }
112
112
  function resolution(disposition, requirement, candidates, selected, capability, reason) {
113
- return deepFreeze({ disposition, requirementId: requirement.id, requirementName: String(requirement.name || ''), classification: requirement.classification, referenceTarget: String(requirement.reference?.target || ''), routeWorkspaceId: String(requirement.routeWorkspaceId || ''), routePath: String(requirement.routePath || ''), sourceRequirementId: String(requirement.sourceRequirementId || ''), recipientReferenceCapability: Boolean(capability), selectedMaterial: selected, candidates: Object.freeze(candidates), reason });
113
+ return deepFreeze({ disposition, requirementId: requirement.id, requirementName: String(requirement.name || ''), classification: requirement.classification, referenceTarget: String(requirement.reference?.target || ''), routeWorkspaceId: String(requirement.routeWorkspaceId || ''), routePath: String(requirement.routePath || ''), sourceRequirementId: String(requirement.sourceRequirementId || ''), sourceWorkspaceId: String(requirement.sourceWorkspaceId || ''), sourcePath: String(requirement.sourcePath || ''), targetWorkspaceId: String(requirement.targetWorkspaceId || ''), targetPath: String(requirement.targetPath || ''), recipientReferenceCapability: Boolean(capability), selectedMaterial: selected, candidates: Object.freeze(candidates), reason });
114
114
  }
115
115
  function serializable(value = {}) { const out = {}; for (const [key, item] of Object.entries(value || {})) if (typeof item !== 'function' && typeof item !== 'undefined') out[key] = item; return out; }
116
116
  function deepFreeze(value) { if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value; if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) return value; for (const child of Object.values(value)) deepFreeze(child); return Object.freeze(value); }
@@ -154,7 +154,7 @@ function renderPayloadMaterialBindings(materials = []) {
154
154
  const referenceTarget = String(item?.referenceTarget || '').trim();
155
155
  const archiveEntry = String(item?.archiveEntry || '').trim();
156
156
  if (!requirementId || !referenceTarget || !archiveEntry) return '';
157
- return `- Requirement Id: ${requirementId}\n - Classification: ${classification || 'detached-material'}\n - Material Reference: ${referenceTarget}\n - Archive Entry: ${archiveEntry}${item?.routeWorkspaceId ? `\n - Route Workspace Id: ${String(item.routeWorkspaceId)}` : ''}${item?.routePath ? `\n - Route Path: ${String(item.routePath)}` : ''}${item?.sourceRequirementId ? `\n - Source Requirement Id: ${String(item.sourceRequirementId)}` : ''}${item?.originalPath ? `\n - Original Path: ${String(item.originalPath)}` : ''}${item?.bytes !== undefined ? `\n - Byte Size: ${Number(item.bytes || 0)}` : ''}${item?.sha256 ? `\n - SHA256: ${String(item.sha256)}` : ''}`;
157
+ return `- Requirement Id: ${requirementId}\n - Classification: ${classification || 'detached-material'}\n - Material Reference: ${referenceTarget}\n - Archive Entry: ${archiveEntry}${item?.routeWorkspaceId ? `\n - Route Workspace Id: ${String(item.routeWorkspaceId)}` : ''}${item?.routePath ? `\n - Route Path: ${String(item.routePath)}` : ''}${item?.sourceRequirementId ? `\n - Source Requirement Id: ${String(item.sourceRequirementId)}` : ''}${item?.sourceWorkspaceId ? `\n - Source Workspace Id: ${String(item.sourceWorkspaceId)}` : ''}${item?.sourcePath ? `\n - Source Path: ${String(item.sourcePath)}` : ''}${item?.targetWorkspaceId ? `\n - Target Workspace Id: ${String(item.targetWorkspaceId)}` : ''}${item?.targetPath ? `\n - Target Path: ${String(item.targetPath)}` : ''}${item?.originalPath ? `\n - Original Path: ${String(item.originalPath)}` : ''}${item?.bytes !== undefined ? `\n - Byte Size: ${Number(item.bytes || 0)}` : ''}${item?.sha256 ? `\n - SHA256: ${String(item.sha256)}` : ''}`;
158
158
  }).filter(Boolean).join('\n');
159
159
  }
160
160
 
@@ -191,17 +191,22 @@ export function virtualCacheMaterial(cache = {}, findings = []) {
191
191
  const byPath = new Map((archive?.entries || []).map((entry) => [String(entry.path || ''), entry]));
192
192
  const files = [];
193
193
  const materialized = [];
194
+ const boundEntries = new Map();
194
195
  for (const item of cache.facts?.materials || []) {
195
196
  const entry = byPath.get(String(item.archiveEntry || ''));
196
197
  if (!entry) {
197
198
  findings.push(finding('error', 'portable.handoff-v2-surface.cache.entry-missing', 'Context cache declaration does not resolve to exactly one archive entry.', { requirementId: item.requirementId || '', archiveEntry: item.archiveEntry || '' }));
198
199
  continue;
199
200
  }
201
+ const archiveEntry = String(item.archiveEntry || '');
202
+ boundEntries.set(archiveEntry, Number(boundEntries.get(archiveEntry) || 0) + 1);
200
203
  if (Number(item.bytes || 0) !== Number(entry.bytes || 0) || String(item.sha256 || '') !== String(entry.sha256 || '')) findings.push(finding('error', 'portable.handoff-v2-surface.cache.entry-identity-mismatch', 'Context cache entry bytes differ from its visible declaration.', { requirementId: item.requirementId || '', archiveEntry: item.archiveEntry || '' }));
201
204
  const packagePath = `recipient.v2.cache/${boundedRecipientToken(cache.facts?.workspaceId || 'workspace')}/${boundedRecipientToken(item.requirementId || item.archiveEntry)}.bin`;
202
205
  files.push(Object.freeze({ path: packagePath, data: entry.data, size: entry.bytes, kind: 'recipient-v2-virtual-cache-material' }));
203
- materialized.push(Object.freeze({ requirementId: String(item.requirementId || ''), classification: String(item.classification || ''), referenceTarget: String(item.referenceTarget || ''), originalPath: String(item.originalPath || ''), packagePath, carrierKind: 'detached-material', bytes: Number(entry.bytes || 0), sha256: String(entry.sha256 || ''), authority: Object.freeze({ carrierDedupBasis: 'visible-cache-artifact-plus-exact-archive-entry-byte-identity' }) }));
206
+ materialized.push(Object.freeze({ requirementId: String(item.requirementId || ''), classification: String(item.classification || ''), referenceTarget: String(item.referenceTarget || ''), routeWorkspaceId: String(item.routeWorkspaceId || ''), routePath: String(item.routePath || ''), sourceRequirementId: String(item.sourceRequirementId || ''), sourceWorkspaceId: String(item.sourceWorkspaceId || ''), sourcePath: String(item.sourcePath || ''), targetWorkspaceId: String(item.targetWorkspaceId || ''), targetPath: String(item.targetPath || ''), originalPath: String(item.originalPath || ''), packagePath, carrierKind: 'detached-material', bytes: Number(entry.bytes || 0), sha256: String(entry.sha256 || ''), authority: Object.freeze({ carrierDedupBasis: 'visible-cache-artifact-plus-exact-archive-entry-byte-identity' }) }));
204
207
  }
208
+ for (const [archiveEntry, count] of boundEntries) if (count !== 1) findings.push(finding('error', 'portable.handoff-v2-surface.cache.entry-binding-ambiguous', 'Each cache archive entry must be claimed by exactly one visible material binding.', { archiveEntry, count }));
209
+ for (const entry of archive?.entries || []) if (!boundEntries.has(String(entry.path || ''))) findings.push(finding('error', 'portable.handoff-v2-surface.cache.entry-unbound', 'Each cache archive entry must be claimed by one visible material binding; opaque unbound dependency bytes are not permitted.', { archiveEntry: String(entry.path || '') }));
205
210
  return Object.freeze({ files: Object.freeze(files), materialized: Object.freeze(materialized) });
206
211
  }
207
212
 
@@ -1,9 +1,9 @@
1
1
  import { finalizeFile } from '../../../export/package.fileMap.js';
2
- import { packageFileByteView } from '../../../export/package.bytes.js';
2
+ import { packageFileByteView, sha256Hex } from '../../../export/package.bytes.js';
3
3
  import { exportFileMapZipUint8Array } from '../../../export/package.zip.js';
4
4
  import { qualifyHandoffWorkspaceTarget } from './workspaceTargetConformance.js';
5
5
  import { inspectStoredWorkspaceArchive } from './workspaceByteProvider.js';
6
- import { renderRecipientV2ExternalPayload, renderRecipientV2Pointer } from './recipientV2.artifacts.js';
6
+ import { RECIPIENT_V2_EXTERNAL_PAYLOAD_SCHEMA_TARGET, renderRecipientV2ExternalPayload, renderRecipientV2Pointer } from './recipientV2.artifacts.js';
7
7
  import { RECIPIENT_V2_READ_PATH } from './recipientV2.topology.js';
8
8
  import { RECIPIENT_V2_ROUTE_SELECTION_AUTHORITY, RECIPIENT_V2_SIBLING_ROUTE_INFERENCE, recipientV2EntryCurrentRead } from './recipientV2.entryContract.js';
9
9
  import { recipientV2TransportFacts } from './recipientV2.transportManifest.js';
@@ -14,8 +14,18 @@ import { RECIPIENT_V2_PACKAGE_V1_FORMAT_ID, RECIPIENT_V2_PACKAGE_V1_ROOT_PATH, R
14
14
  import { renderHandoffPackageV1 } from './recipientV2.packageV1.contract.js';
15
15
  import { inspectRecipientFacingV2PackageV1 } from './recipientV2.packageV1.inspect.js';
16
16
  import { blocked, deepFreeze, exactFile, workspaceSchemaTarget } from './recipientV2.packageV1.shared.js';
17
+ import { sealPasswordWorkspacePayload } from '../../../transport/secureTransportV1.js';
18
+ import { renderTransportEnvelopeV1, TRANSPORT_ENVELOPE_V1_ROLE } from './transportEnvelopeV1.js';
17
19
 
18
- export function buildRecipientFacingV2PackageV1(input = {}) {
20
+ export function buildRecipientFacingV2PackageV1(input = {}) { return buildRecipientFacingV2PackageV1Prepared(input, new Map()); }
21
+
22
+ export async function buildRecipientFacingV2PackageV1Secure(input = {}) {
23
+ const prepared = await prepareSealedWorkspaceBindings(input);
24
+ if (prepared.status !== 'ready') return prepared;
25
+ return buildRecipientFacingV2PackageV1Prepared(input, prepared.byWorkspaceId);
26
+ }
27
+
28
+ function buildRecipientFacingV2PackageV1Prepared(input = {}, sealedByWorkspaceId = new Map()) {
19
29
  const sourceSurface = input.sourceSurface || null;
20
30
  const descriptor = input.descriptor || input.bundle?.handoffClosure || {};
21
31
  const carrier = input.carrierProjection || {};
@@ -29,10 +39,11 @@ export function buildRecipientFacingV2PackageV1(input = {}) {
29
39
  const route = selected[0];
30
40
  const sourceWorkspaceById = new Map((sourceSurface.topology?.workspaces || []).map((item) => [String(item.workspaceId || ''), item]));
31
41
  const sourceByPath = new Map((sourceSurface.files || []).map((file) => [String(file.path || ''), file]));
32
- // The package-v1 carrier preserves the complete inherited Workspace source chain.
33
- // Route closure remains selected and minimal, but complete package-local Workspace
34
- // bindings are not pruned merely because one carried Workspace is not on the
35
- // selected Handoff's pre-Handoff closure.
42
+ // The package-v1 carrier preserves every complete Workspace source explicitly
43
+ // selected for carriage. Route closure remains selected and minimal, but an
44
+ // explicitly carried Workspace is not pruned merely because it is outside the
45
+ // selected Handoff's pre-Handoff closure. Qualified package-parent Workspaces
46
+ // used only as read-only requirement providers remain detached closure material.
36
47
  const workspaceIds = [...sourceWorkspaceById.keys()].filter(Boolean).sort();
37
48
  const workspacePlans = workspaceIds.map((workspaceId, index) => Object.freeze({ workspaceId, ordinal: index + 3, prefix: `001-${index + 3}`, slug: safeToken(workspaceId) }));
38
49
  const files = [];
@@ -54,18 +65,33 @@ export function buildRecipientFacingV2PackageV1(input = {}) {
54
65
  const targetQualification = qualifyHandoffWorkspaceTarget({ targetPath: innerPath, targetData, entries: parsed.entries || [] });
55
66
  if (targetQualification.state !== 'qualified') { findings.push(finding('error', 'portable.handoff-package-v1.workspace.target-unqualified', 'Exact Workspace artifact bytes inside the snapshot do not qualify.', { workspaceId: plan.workspaceId, reasons: targetQualification.reasons || [] })); continue; }
56
67
  const workspacePath = `${plan.prefix}-${plan.slug}.workspace.md`;
57
- const archivePath = `${plan.prefix}-${plan.slug}.workspace.zip`;
58
68
  const workspaceFile = exactFile(workspacePath, targetData, 'recipient-v2-package-v1-exact-workspace-artifact', 'text/markdown');
59
- const archiveFile = exactFile(archivePath, archiveData, 'recipient-v2-package-v1-complete-workspace-snapshot', 'application/zip');
60
- files.push(workspaceFile, archiveFile);
69
+ files.push(workspaceFile);
61
70
  const parent = recipientV2ParentAuthority(workspaceFile, 'tiinex.workspace.v1', workspaceSchemaTarget(targetQualification), createdAt);
62
- const projection = Object.freeze({
63
- workspaceId: plan.workspaceId, ordinal: plan.ordinal, workspacePath, workspaceSha256: workspaceFile.sha256,
64
- archivePath, archiveSha256: archiveFile.sha256, sourceWorkspaceTargetInnerPath: innerPath,
65
- sourceWorkspaceTargetSha256: workspaceFile.sha256, sourceWorkspaceTargetBytes: workspaceFile.bytes, coverage: 'complete'
66
- });
67
- topology.workspaces.push(projection);
68
- workspaceById.set(plan.workspaceId, { ...projection, file: workspaceFile, parent });
71
+ const sealedPrepared = sealedByWorkspaceId.get(plan.workspaceId) || null;
72
+ if (sealedPrepared) {
73
+ const protectedPayloadPath = `${plan.prefix}-${plan.slug}.protected.bin`;
74
+ const protectedPayloadDescriptorPath = `${plan.prefix}-${plan.slug}.protected-payload.trace.md`;
75
+ const transportEnvelopePath = `${plan.prefix}-${plan.slug}.transport-envelope.trace.md`;
76
+ const projection = Object.freeze({
77
+ workspaceId: plan.workspaceId, ordinal: plan.ordinal, workspacePath, workspaceSha256: workspaceFile.sha256,
78
+ snapshotKind: 'password-sealed-workspace-byte-tree', bindingState: 'sealed', protectedPayloadPath, protectedPayloadDescriptorPath, transportEnvelopePath,
79
+ protectedPayloadBytes: sealedPrepared.protectedPayload.byteLength, sourceWorkspaceTargetSha256: workspaceFile.sha256, sourceWorkspaceTargetBytes: workspaceFile.bytes, coverage: 'complete'
80
+ });
81
+ topology.workspaces.push(projection);
82
+ workspaceById.set(plan.workspaceId, { ...projection, file: workspaceFile, parent, sealed: true });
83
+ } else {
84
+ const archivePath = `${plan.prefix}-${plan.slug}.workspace.zip`;
85
+ const archiveFile = exactFile(archivePath, archiveData, 'recipient-v2-package-v1-complete-workspace-snapshot', 'application/zip');
86
+ files.push(archiveFile);
87
+ const projection = Object.freeze({
88
+ workspaceId: plan.workspaceId, ordinal: plan.ordinal, workspacePath, workspaceSha256: workspaceFile.sha256,
89
+ archivePath, archiveSha256: archiveFile.sha256, sourceWorkspaceTargetInnerPath: innerPath,
90
+ sourceWorkspaceTargetSha256: workspaceFile.sha256, sourceWorkspaceTargetBytes: workspaceFile.bytes, coverage: 'complete'
91
+ });
92
+ topology.workspaces.push(projection);
93
+ workspaceById.set(plan.workspaceId, { ...projection, file: workspaceFile, parent });
94
+ }
69
95
  }
70
96
 
71
97
  if (findings.some((item) => item.severity === 'error')) return blocked('workspace-binding-blocked', findings);
@@ -81,6 +107,15 @@ export function buildRecipientFacingV2PackageV1(input = {}) {
81
107
  topology.root = Object.freeze({ path: packageFile.path, sha256: packageFile.sha256 });
82
108
  const packageParent = recipientV2ParentAuthority(packageFile, RECIPIENT_V2_PACKAGE_V1_SCHEMA_ID, RECIPIENT_V2_PACKAGE_V1_SCHEMA_TARGET, createdAt);
83
109
 
110
+ for (const workspace of topology.workspaces.filter((item) => item.bindingState === 'sealed')) {
111
+ const prepared = sealedByWorkspaceId.get(workspace.workspaceId);
112
+ const protectedFile = exactFile(workspace.protectedPayloadPath, prepared.protectedPayload, 'recipient-v2-package-v1-password-sealed-workspace-payload', 'application/octet-stream');
113
+ const payloadFacts = recipientV2TransportFacts('password-sealed Workspace protected payload', { workspaceId: workspace.workspaceId, archivePath: protectedFile.path, archiveBytes: protectedFile.bytes, archiveSha256: protectedFile.sha256 });
114
+ const payloadArtifact = finalizeFile({ path: workspace.protectedPayloadDescriptorPath, kind: 'protected-workspace-payload-descriptor', logicalKind: 'recipient-v2-package-v1-password-sealed-payload-descriptor', mediaType: 'text/markdown', transportFacts: payloadFacts, content: renderRecipientV2ExternalPayload({ createdAt, parent: packageParent, title: `Protected Workspace Payload — ${workspace.workspaceId}`, summary: 'Exact password-sealed Workspace ciphertext. Protected Workspace filenames, directories, inner artifact paths, and plaintext inventory remain encrypted.', label: `${workspace.workspaceId} protected Workspace payload`, kind: 'password-sealed-workspace-ciphertext', mediaType: 'application/octet-stream', format: prepared.envelope.profile.contentEncryptionParameters.binaryFraming, role: 'password-sealed Workspace protected payload', workspaceId: workspace.workspaceId, location: protectedFile.path, bytes: protectedFile.bytes, sha256: protectedFile.sha256 }) });
115
+ const envelopeArtifact = finalizeFile({ path: workspace.transportEnvelopePath, kind: 'password-sealed-workspace-transport-envelope', logicalKind: 'recipient-v2-package-v1-transport-envelope', mediaType: 'text/markdown', transportFacts: recipientV2TransportFacts(TRANSPORT_ENVELOPE_V1_ROLE, { workspaceId: workspace.workspaceId, workspaceArtifactPath: workspace.workspacePath, protectedPayloadDescriptorPath: workspace.protectedPayloadDescriptorPath, workspaceBindingValue: prepared.envelope.workspaceBindingValue, profileId: prepared.envelope.profile.profileId, profileVersion: prepared.envelope.profile.profileVersion }), content: renderTransportEnvelopeV1({ createdAt, parent: packageParent, workspaceArtifactPath: workspace.workspacePath, protectedPayloadDescriptorPath: workspace.protectedPayloadDescriptorPath, envelope: prepared.envelope }) });
116
+ files.push(payloadArtifact, envelopeArtifact, protectedFile);
117
+ }
118
+
84
119
  const bootstrapSource = (input.bundle?.files || []).filter((file) => String(file.path || '').startsWith('tiinex.bootstrap/'));
85
120
  if (bootstrapSource.length) {
86
121
  const bootstrap = buildRecipientV2BootstrapCarrier(bootstrapSource, createdAt, findings, packageParent);
@@ -101,15 +136,16 @@ export function buildRecipientFacingV2PackageV1(input = {}) {
101
136
  const cacheFile = finalizeFile({ path: archivePath, kind: 'handoff-material-cache', logicalKind: 'recipient-v2-package-v1-workspace-dependency-cache', mediaType: 'application/zip', data: cacheBytes });
102
137
  const cacheFacts = {
103
138
  workspaceId: owningWorkspace.workspaceId, archivePath, archiveBytes: cacheFile.bytes, archiveSha256: cacheFile.sha256,
104
- materials: selectedDetached.map((item, index) => ({ requirementId: item.requirementId, classification: item.classification, referenceTarget: item.referenceTarget, routeWorkspaceId: item.routeWorkspaceId, routePath: item.routePath, sourceRequirementId: item.sourceRequirementId, originalPath: item.originalPath, archiveEntry: cacheEntries[index].path, bytes: item.bytes, sha256: item.sha256 }))
139
+ materials: selectedDetached.map((item, index) => ({ requirementId: item.requirementId, classification: item.classification, referenceTarget: item.referenceTarget, routeWorkspaceId: item.routeWorkspaceId, routePath: item.routePath, sourceRequirementId: item.sourceRequirementId, sourceWorkspaceId: item.sourceWorkspaceId, sourcePath: item.sourcePath, targetWorkspaceId: item.targetWorkspaceId, targetPath: item.targetPath, originalPath: item.originalPath, archiveEntry: cacheEntries[index].path, bytes: item.bytes, sha256: item.sha256 }))
105
140
  };
106
- const cacheArtifact = finalizeFile({ path: artifactPath, kind: 'tiinex-external-payload-artifact', logicalKind: 'recipient-v2-package-v1-workspace-dependency-cache-reference', mediaType: 'text/markdown', transportFacts: recipientV2TransportFacts('workspace-scoped Handoff dependency cache', cacheFacts), content: renderRecipientV2ExternalPayload({ createdAt, parent: owningWorkspace.parent, title: `Workspace Dependency Cache — ${owningWorkspace.workspaceId}`, summary: 'Exact route-bounded dependency bytes not satisfied by a complete carried Workspace snapshot.', label: `${owningWorkspace.workspaceId} Handoff dependency cache`, kind: 'zip export', role: 'workspace-scoped Handoff dependency cache', location: archivePath, bytes: cacheFile.bytes, sha256: cacheFile.sha256, facts: cacheFacts }) });
141
+ const cacheArtifact = finalizeFile({ path: artifactPath, kind: 'tiinex-external-payload-artifact', logicalKind: 'recipient-v2-package-v1-workspace-dependency-cache-reference', mediaType: 'text/markdown', transportFacts: recipientV2TransportFacts('workspace-scoped Handoff dependency cache', cacheFacts), content: renderRecipientV2ExternalPayload({ createdAt, parent: owningWorkspace.parent, title: `Workspace Dependency Cache — ${owningWorkspace.workspaceId}`, summary: 'Exact route-bounded dependency bytes not satisfied by a complete carried Workspace snapshot.', label: `${owningWorkspace.workspaceId} Handoff dependency cache`, kind: 'zip export', role: 'workspace-scoped Handoff dependency cache', location: archivePath, bytes: cacheFile.bytes, sha256: cacheFile.sha256, materials: cacheFacts.materials }) });
107
142
  files.push(cacheArtifact, cacheFile);
108
143
  cache = { workspaceId: owningWorkspace.workspaceId, artifactPath, archivePath, materials: cacheFacts.materials, parent: recipientV2ParentAuthority(cacheArtifact, 'tiinex.external.payload.v1', RECIPIENT_V2_EXTERNAL_PAYLOAD_SCHEMA_TARGET, createdAt) };
109
144
  topology.caches.push(Object.freeze({ workspaceId: cache.workspaceId, artifactPath, archivePath, materials: cache.materials }));
110
145
  }
111
146
 
112
147
  if (!owningWorkspace) findings.push(finding('error', 'portable.handoff-package-v1.route.workspace-unresolved', 'Selected Handoff route owning Workspace is not carried.'));
148
+ else if (owningWorkspace.sealed) findings.push(finding('error', 'portable.handoff-package-v1.route.workspace-sealed-forbidden', 'Selected authoritative Handoff route Workspace must remain a clear verified snapshot in Secure Transport V1.', { workspaceId: owningWorkspace.workspaceId }));
113
149
  else {
114
150
  const plan = workspacePlans.find((item) => item.workspaceId === owningWorkspace.workspaceId);
115
151
  const binding = bindingForWorkspace(descriptor, owningWorkspace.workspaceId);
@@ -144,4 +180,42 @@ export function buildRecipientFacingV2PackageV1(input = {}) {
144
180
  return Object.freeze({ status: inspection.status === 'valid' && !findings.some((item) => item.severity === 'error') ? 'ready' : 'blocked', files: Object.freeze(sortedFiles), topology: deepFreeze(topology), inspection, findings: Object.freeze([...findings, ...(inspection.findings || [])]), boundary: 'Recipient-facing tiinex.handoff.package.v1 carrier with exact complete Workspace artifacts/snapshots, bootstrap/cache External Payload ownership retained, and no redundant Workspace External Payload or Workspace Representation companions.' });
145
181
  }
146
182
 
183
+
184
+ async function prepareSealedWorkspaceBindings(input = {}) {
185
+ const protections = Array.isArray(input.sealedWorkspaces) ? input.sealedWorkspaces : [];
186
+ if (!protections.length) return Object.freeze({ status: 'ready', byWorkspaceId: new Map() });
187
+ const sourceSurface = input.sourceSurface || null;
188
+ const carrier = input.carrierProjection || {};
189
+ if (!sourceSurface || sourceSurface.status !== 'ready') return blocked('source-surface-unready', sourceSurface?.findings || []);
190
+ const routes = (carrier.routes || []).filter((route) => route.state === 'qualified');
191
+ const selector = String(input.routeSelector || input.routeId || '').trim();
192
+ const selected = selector ? routes.filter((route) => route.id === selector || route.workspaceRelativePath === selector || `${String(route.workspaceId || '')}:${String(route.workspaceRelativePath || '')}` === selector || `handoff-route:${String(route.workspaceId || '')}:${String(route.workspaceRelativePath || '')}` === selector) : routes;
193
+ if (selected.length !== 1) return blocked('route-selection-unresolved', [finding('error', 'portable.handoff-package-v1.route-selection-unresolved', 'Secure package manufacture requires exactly one selected Handoff route.', { count: selected.length })]);
194
+ const routeWorkspaceId = String(selected[0].workspaceId || '');
195
+ const sourceWorkspaceById = new Map((sourceSurface.topology?.workspaces || []).map((item) => [String(item.workspaceId || ''), item]));
196
+ const sourceByPath = new Map((sourceSurface.files || []).map((file) => [String(file.path || ''), file]));
197
+ const byWorkspaceId = new Map();
198
+ const findings = [];
199
+ for (const protection of protections) {
200
+ const workspaceId = String(protection?.workspaceId || '').trim();
201
+ if (!workspaceId || byWorkspaceId.has(workspaceId)) { findings.push(finding('error', 'secure-transport.manufacture.workspace-id-invalid', 'Each sealed Workspace selection must identify one unique carried Workspace.', { workspaceId })); continue; }
202
+ if (workspaceId === routeWorkspaceId) { findings.push(finding('error', 'secure-transport.manufacture.route-workspace-must-remain-clear', 'Secure Transport V1 does not permit sealing the authoritative selected Handoff route Workspace.', { workspaceId })); continue; }
203
+ const source = sourceWorkspaceById.get(workspaceId);
204
+ const sourceArchive = source ? sourceByPath.get(String(source.archivePath || '')) : null;
205
+ if (!source || String(source.coverage || '') !== 'complete' || !sourceArchive) { findings.push(finding('error', 'secure-transport.manufacture.workspace-unresolved', 'Selected sealed Workspace must resolve to one complete qualified source snapshot.', { workspaceId })); continue; }
206
+ const archiveData = packageFileByteView(sourceArchive);
207
+ const parsed = inspectStoredWorkspaceArchive(archiveData, { ownedBytes: true });
208
+ const innerPath = String(source.sourceWorkspaceTargetInnerPath || '');
209
+ const targetMatches = (parsed.entries || []).filter((entry) => entry.path === innerPath);
210
+ if (parsed.state !== 'qualified' || targetMatches.length !== 1) { findings.push(finding('error', 'secure-transport.manufacture.workspace-source-unqualified', 'Selected sealed Workspace source archive/Workspace artifact correlation does not qualify.', { workspaceId })); continue; }
211
+ const targetData = packageFileByteView({ data: targetMatches[0].data });
212
+ const targetQualification = qualifyHandoffWorkspaceTarget({ targetPath: innerPath, targetData, entries: parsed.entries || [] });
213
+ if (targetQualification.state !== 'qualified') { findings.push(finding('error', 'secure-transport.manufacture.workspace-target-unqualified', 'Selected sealed Workspace artifact does not qualify before encryption.', { workspaceId, reasons: targetQualification.reasons || [] })); continue; }
214
+ const sealed = await sealPasswordWorkspacePayload({ plaintext: archiveData, workspaceBindingValue: sha256Hex(targetData), recipients: protection.recipients || [], crypto: input.crypto });
215
+ if (sealed.state !== 'sealed') { findings.push(finding('error', 'secure-transport.manufacture.seal-failed', 'Selected Workspace could not be sealed under the supported V1 profile.', { workspaceId, reason: sealed.reason || sealed.state })); continue; }
216
+ byWorkspaceId.set(workspaceId, sealed);
217
+ }
218
+ return findings.some((item) => item.severity === 'error') ? blocked('secure-workspace-preparation-blocked', findings) : Object.freeze({ status: 'ready', byWorkspaceId, findings: Object.freeze(findings) });
219
+ }
220
+
147
221
  function hasBootstrap(bundle = {}) { return (bundle.files || []).some((file) => String(file.path || '').startsWith('tiinex.bootstrap/')); }
@@ -24,7 +24,7 @@ export function renderHandoffPackageV1(input = {}) {
24
24
  const packageRole = [HANDOFF_PACKAGE_ROLE, WORKSPACE_PACKAGE_ROLE].includes(String(input.packageRole || '')) ? String(input.packageRole) : HANDOFF_PACKAGE_ROLE;
25
25
  const routeProfile = routeProfileForRole(packageRole);
26
26
  const workspaces = input.workspaces || [];
27
- const workspaceBlocks = workspaces.map((workspace) => `- ${workspace.workspaceId}\n - Workspace Id: ${workspace.workspaceId}\n - Workspace Artifact: [${workspace.workspaceId} Workspace](${workspace.workspacePath})\n - Snapshot Path: [${workspace.workspaceId} Snapshot](${workspace.archivePath})\n - Workspace Artifact Inner Path: \`${workspace.sourceWorkspaceTargetInnerPath}\`\n - Snapshot Kind: exact-workspace-byte-tree-archive\n - Coverage: complete\n - Binding State: verified\n - Integrity Method: sha256\n - Integrity Value: ${workspace.archiveSha256}\n - Byte Size: ${workspace.archiveBytes || ''}`.replace(/\n - Byte Size: $/, '')).join('\n');
27
+ const workspaceBlocks = workspaces.map((workspace) => renderWorkspaceBinding(workspace)).join('\n');
28
28
  const unsigned = `# Continuity Context\n\n- Envelope Schema: [tiinex.root.v1](${RECIPIENT_V2_ROOT_SCHEMA_TARGET})\n- Current\n - Current Schema: [tiinex.handoff.package.v1](${RECIPIENT_V2_PACKAGE_V1_SCHEMA_TARGET})\n - Created At: ${createdAt}\n - Summary: Recipient-facing self-contained Handoff carrier identity, discovery, exact complete Workspace snapshot bindings, and fail-closed qualification boundary.\n\n---\n\n# Handoff Package\n\n## Package Identity\n\n- Package Role: ${packageRole}\n- Carrier Kind: self-contained\n\n## Bootstrap Exposure\n\n- Start Artifact: [Start](${input.startPath || RECIPIENT_V2_READ_PATH})\n${input.bootstrapPath ? `- Tooling Bootstrap Descriptor: [Bootstrap](${input.bootstrapPath})\n` : '- Tooling Bootstrap Descriptor: [Bootstrap](001-2-bootstrap.trace.md)\n'}- Bootstrap Rule: start-then-qualified-bootstrap\n\n## Workspace Snapshot Bindings\n\n${workspaceBlocks || '- none'}\n\n## Route Discovery\n\n- Route Placement Rule: ${routeProfile.routePlacementRule}\n- Continue-From Rule: ${routeProfile.continueFromRule}\n- Pre-Handoff Closure Rule: ${routeProfile.preHandoffClosureRule}\n\n## Carrier Continuity\n\n- Carrier Dimension: ${String(lineage.dimension || '001')}\n${lineage.parentDimension ? `- Parent Carrier Dimension: ${String(lineage.parentDimension)}\n` : ''}- Carrier Checkpoint: ${String(lineage.checkpointKind || 'progression')}\n${lineage.majorReason ? `- Major Reason: ${String(lineage.majorReason)}\n` : ''}- Carrier Profile Id: ${carrierProfile.id || 'none'}\n- Required Major Workspace Ids: ${carrierProfile.requiredMajorWorkspaceIds.length ? carrierProfile.requiredMajorWorkspaceIds.join(', ') : 'none'}\n\n## Qualification Boundary\n\n- Receiver Qualification: reverify-carried-authority-and-bytes\n- Failure Policy: fail-closed\n- Derived Inventory Authority: none\n\n## Interpretation Limits\n\n- Does Not Mean: package placement, discovery ancestry, or byte integrity creates Handoff, Workspace, Role, participation, acceptance, provenance, or Parent authority\n- Must Not Be Used To Claim: carrier convenience metadata overrides the authoritative contained artifacts or exact qualified source bytes\n- Generic Payload Boundary: bootstrap/cache payloads retain explicit External Payload ownership when required; complete Workspace snapshot binding is package-local only\n- Generic Representation Boundary: bounded, partial, independently selectable, multi-representation, or external-lifecycle Workspace representations require the generic Workspace Representation contract\n- Carrier Profile Boundary: required Workspace identifiers come only from the explicit carrier profile; literal project Workspace names have no generic semantic meaning\n\n---\n\n# Continuity Integrity\n\n- [sha256-base64url-c14n-v2](${C14N_V2_VALIDATOR_TARGET})\n - Towards: self\n - Value: pending\n`;
29
29
  const sealed = sealC14nV2Self(unsigned);
30
30
  if (sealed.state !== 'sealed') throw new Error(`portable.handoff-package-v1.integrity.seal-failed:${sealed.reason || sealed.state}`);
@@ -58,6 +58,7 @@ export function validatePackageFields(value, findings) {
58
58
  for (const [key, expected] of exact) if (String(value?.[key] || '') !== expected) findings.push(finding('error', `portable.handoff-package-v1.field.${key}.invalid`, 'Handoff package v1 required closed-domain field is missing or invalid.', { field: key, expected, observed: String(value?.[key] || '') }));
59
59
  if (!value?.startPath || !value?.bootstrapPath) findings.push(finding('error', 'portable.handoff-package-v1.bootstrap-exposure-invalid', 'Start Artifact and Tooling Bootstrap Descriptor must be package-local links.'));
60
60
  if (!Array.isArray(value?.workspaces) || !value.workspaces.length) findings.push(finding('error', 'portable.handoff-package-v1.workspace-bindings-missing', 'Handoff package v1 requires at least one complete Workspace snapshot binding.'));
61
+ for (const binding of value?.workspaces || []) validateWorkspaceBinding(binding, findings);
61
62
  if (!validCarrierDimension(value?.carrierDimension)) findings.push(finding('error', 'portable.handoff-package-v1.carrier-dimension-invalid', 'Carrier Dimension must be a canonical numeric hyphen path.', { value: value?.carrierDimension || '' }));
62
63
  if (value?.parentCarrierDimension && !validCarrierDimension(value.parentCarrierDimension)) findings.push(finding('error', 'portable.handoff-package-v1.parent-carrier-dimension-invalid', 'Parent Carrier Dimension must be a canonical numeric hyphen path.', { value: value.parentCarrierDimension || '' }));
63
64
  if (!['progression', 'major'].includes(String(value?.carrierCheckpoint || ''))) findings.push(finding('error', 'portable.handoff-package-v1.carrier-checkpoint-invalid', 'Carrier Checkpoint must be progression or major.'));
@@ -82,12 +83,16 @@ function parseWorkspaceBindings(text = '') {
82
83
  if (key === 'Workspace Id') current.workspaceId = value;
83
84
  else if (key === 'Workspace Artifact') current.workspaceArtifactPath = markdownTarget(value);
84
85
  else if (key === 'Snapshot Path') current.snapshotPath = markdownTarget(value);
86
+ else if (key === 'Protected Payload Descriptor') current.protectedPayloadDescriptorPath = markdownTarget(value);
87
+ else if (key === 'Transport Envelope') current.transportEnvelopePath = markdownTarget(value);
85
88
  else if (key === 'Workspace Artifact Inner Path') current.workspaceArtifactInnerPath = unquote(value);
86
89
  else if (key === 'Snapshot Kind') current.snapshotKind = value;
87
90
  else if (key === 'Coverage') current.coverage = value;
88
91
  else if (key === 'Binding State') current.bindingState = value;
89
92
  else if (key === 'Integrity Method') current.integrityMethod = value;
90
93
  else if (key === 'Integrity Value') current.integrityValue = value;
94
+ else if (key === 'Protection State') current.protectionState = value;
95
+ else if (key === 'Post-Open Correlation Rule') current.postOpenCorrelationRule = value;
91
96
  else if (key === 'Byte Size') current.byteSize = /^\d+$/.test(value) ? Number(value) : null;
92
97
  }
93
98
  out.push(current);
@@ -95,6 +100,30 @@ function parseWorkspaceBindings(text = '') {
95
100
  return out.map((item) => Object.freeze({ ...item, byteSize: item.byteSize ?? null }));
96
101
  }
97
102
 
103
+
104
+ function renderWorkspaceBinding(workspace = {}) {
105
+ const sealed = String(workspace.snapshotKind || '') === 'password-sealed-workspace-byte-tree' || String(workspace.bindingState || '') === 'sealed';
106
+ if (sealed) return `- ${workspace.workspaceId}\n - Workspace Id: ${workspace.workspaceId}\n - Workspace Artifact: [${workspace.workspaceId} Workspace](${workspace.workspacePath})\n - Protected Payload Descriptor: [${workspace.workspaceId} Protected Payload](${workspace.protectedPayloadDescriptorPath})\n - Transport Envelope: [${workspace.workspaceId} Transport Envelope](${workspace.transportEnvelopePath})\n - Snapshot Kind: password-sealed-workspace-byte-tree\n - Coverage: complete\n - Binding State: sealed\n - Protection State: password-sealed\n - Post-Open Correlation Rule: unique-exact-workspace-artifact-byte-match`;
107
+ return `- ${workspace.workspaceId}\n - Workspace Id: ${workspace.workspaceId}\n - Workspace Artifact: [${workspace.workspaceId} Workspace](${workspace.workspacePath})\n - Snapshot Path: [${workspace.workspaceId} Snapshot](${workspace.archivePath})\n - Workspace Artifact Inner Path: \`${workspace.sourceWorkspaceTargetInnerPath}\`\n - Snapshot Kind: exact-workspace-byte-tree-archive\n - Coverage: complete\n - Binding State: verified\n - Integrity Method: sha256\n - Integrity Value: ${workspace.archiveSha256}\n - Byte Size: ${workspace.archiveBytes || ''}`.replace(/\n - Byte Size: $/, '');
108
+ }
109
+
110
+ function validateWorkspaceBinding(binding = {}, findings = []) {
111
+ const workspaceId = String(binding.workspaceId || '');
112
+ if (!workspaceId || !binding.workspaceArtifactPath || String(binding.coverage || '') !== 'complete') findings.push(finding('error', 'portable.handoff-package-v1.workspace-binding-common-invalid', 'Workspace binding requires Workspace Id, Workspace Artifact, and complete coverage.', { workspaceId }));
113
+ const kind = String(binding.snapshotKind || '');
114
+ if (kind === 'exact-workspace-byte-tree-archive') {
115
+ if (String(binding.bindingState || '') !== 'verified' || !binding.snapshotPath || !binding.workspaceArtifactInnerPath || String(binding.integrityMethod || '') !== 'sha256' || !/^[0-9a-f]{64}$/.test(String(binding.integrityValue || ''))) findings.push(finding('error', 'portable.handoff-package-v1.workspace-binding-clear-invalid', 'Clear Workspace binding requires exact complete verified sha256 snapshot fields.', { workspaceId }));
116
+ if (binding.protectedPayloadDescriptorPath || binding.transportEnvelopePath || binding.protectionState || binding.postOpenCorrelationRule) findings.push(finding('error', 'portable.handoff-package-v1.workspace-binding-clear-sealed-fields-forbidden', 'Clear Workspace binding must not expose sealed-only binding fields.', { workspaceId }));
117
+ return;
118
+ }
119
+ if (kind === 'password-sealed-workspace-byte-tree') {
120
+ if (String(binding.bindingState || '') !== 'sealed' || !binding.protectedPayloadDescriptorPath || !binding.transportEnvelopePath || String(binding.protectionState || '') !== 'password-sealed' || String(binding.postOpenCorrelationRule || '') !== 'unique-exact-workspace-artifact-byte-match') findings.push(finding('error', 'portable.handoff-package-v1.workspace-binding-sealed-invalid', 'Sealed Workspace binding requires exact password-sealed binding fields.', { workspaceId }));
121
+ if (binding.snapshotPath || binding.workspaceArtifactInnerPath || binding.integrityMethod || binding.integrityValue) findings.push(finding('error', 'portable.handoff-package-v1.workspace-binding-sealed-clear-fields-forbidden', 'Sealed Workspace binding must not expose clear snapshot path, inner Workspace path, or duplicated payload integrity fields.', { workspaceId }));
122
+ return;
123
+ }
124
+ findings.push(finding('error', 'portable.handoff-package-v1.workspace-binding-kind-invalid', 'Workspace binding Snapshot Kind is unsupported.', { workspaceId, snapshotKind: kind }));
125
+ }
126
+
98
127
  function normalizeProfileId(value = '') {
99
128
  const text = String(value || '').trim();
100
129
  return text.toLowerCase() === 'none' ? '' : text;
@@ -7,6 +7,7 @@ import { parentTrace } from './recipientV2.lineage.js';
7
7
  import { finding } from './recipientV2.topology.materials.js';
8
8
  import { RECIPIENT_V2_PACKAGE_V1_FORMAT_ID, RECIPIENT_V2_PACKAGE_V1_ROOT_PATH } from './recipientV2.packageV1.constants.js';
9
9
  import { currentSchemaId, decodeUtf8, field, markdownTarget, numericDimension, oneFile, sectionText, unquote } from './recipientV2.packageV1.shared.js';
10
+ import { parseTransportEnvelopeV1, TRANSPORT_ENVELOPE_V1_ROLE, TRANSPORT_ENVELOPE_V1_SCHEMA_ID } from './transportEnvelopeV1.js';
10
11
 
11
12
  export function validateRouteClosure(routePointers, endpointPointers, participantPointers, caches, workspaces, findings) {
12
13
  const roles = new Map([...(endpointPointers || []), ...(participantPointers || [])].map((item) => [item.path, item]));
@@ -55,7 +56,7 @@ function validateNumericParentChain(pointer, roles, cacheByPath, workspace, find
55
56
  }
56
57
  }
57
58
 
58
- export function deriveVisibleFacts({ markdown = '', schemaId = '', packageContract = null, index = new Map() } = {}) {
59
+ export function deriveVisibleFacts({ file = null, markdown = '', schemaId = '', packageContract = null, index = new Map() } = {}) {
59
60
  if (schemaId === 'tiinex.pointer.v1') {
60
61
  const visible = parseRecipientV2Pointer(markdown);
61
62
  const role = String(visible.role || '');
@@ -84,6 +85,11 @@ export function deriveVisibleFacts({ markdown = '', schemaId = '', packageContra
84
85
  }
85
86
  return base;
86
87
  }
88
+ if (schemaId === TRANSPORT_ENVELOPE_V1_SCHEMA_ID) {
89
+ const visible = parseTransportEnvelopeV1(markdown);
90
+ const workspaceId = (packageContract?.workspaces || []).find((item) => item.transportEnvelopePath === file?.path)?.workspaceId || '';
91
+ return { factsFormat: 'portable-recipient-v2', factsVersion: 1, role: TRANSPORT_ENVELOPE_V1_ROLE, workspaceId: String(workspaceId || ''), workspaceArtifactPath: visible.workspaceArtifactPath, protectedPayloadDescriptorPath: visible.protectedPayloadDescriptorPath, workspaceBindingValue: visible.workspaceBindingValue, profileId: visible.profile?.profileId || '', profileVersion: Number(visible.profile?.profileVersion || 0) };
92
+ }
87
93
  if (schemaId === 'tiinex.external.payload.v1') {
88
94
  const visible = parseRecipientV2ExternalPayload(markdown);
89
95
  const role = String(visible.payloadRole || '');
@@ -118,7 +124,7 @@ function parsePayloadMaterials(section = '') {
118
124
  if (first) { if (current) out.push(current); current = { requirementId: first[1].trim() }; continue; }
119
125
  const match = line.match(/^\s{2}-\s+([^:]+):\s*(.*)$/); if (!current || !match) continue;
120
126
  const key = match[1].trim(), value = match[2].trim();
121
- if (key === 'Classification') current.classification = value; else if (key === 'Material Reference') current.referenceTarget = value; else if (key === 'Archive Entry') current.archiveEntry = value; else if (key === 'Route Workspace Id') current.routeWorkspaceId = value; else if (key === 'Route Path') current.routePath = value; else if (key === 'Source Requirement Id') current.sourceRequirementId = value; else if (key === 'Original Path') current.originalPath = value; else if (key === 'Byte Size') current.bytes = Number(value || 0); else if (key === 'SHA256') current.sha256 = value;
127
+ if (key === 'Classification') current.classification = value; else if (key === 'Material Reference') current.referenceTarget = value; else if (key === 'Archive Entry') current.archiveEntry = value; else if (key === 'Route Workspace Id') current.routeWorkspaceId = value; else if (key === 'Route Path') current.routePath = value; else if (key === 'Source Requirement Id') current.sourceRequirementId = value; else if (key === 'Source Workspace Id') current.sourceWorkspaceId = value; else if (key === 'Source Path') current.sourcePath = value; else if (key === 'Target Workspace Id') current.targetWorkspaceId = value; else if (key === 'Target Path') current.targetPath = value; else if (key === 'Original Path') current.originalPath = value; else if (key === 'Byte Size') current.bytes = Number(value || 0); else if (key === 'SHA256') current.sha256 = value;
122
128
  }
123
129
  if (current) out.push(current);
124
130
  return out;