@tiinex/core 0.3.0 → 0.5.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 +3 -1
  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
@@ -1,7 +1,9 @@
1
1
  import { packageFileByteView, packageFileBytes, sha256Hex } from '../../../export/package.bytes.js';
2
+ import { parseArtifactMarkdown } from '../../../artifacts/artifact.parse.js';
2
3
  import { canonicalC14nV2SelfState } from '../../../integrity/integrity.c14nV2.js';
3
4
  import { buildHandoffCarrierProjection } from './carrierProjection.js';
4
- import { buildHandoffWorkspaceByteProvider, inspectStoredWorkspaceArchive } from './workspaceByteProvider.js';
5
+ import { buildHandoffWorkspaceByteProvider, inspectStoredWorkspaceArchive, resolveHandoffWorkspaceEntry } from './workspaceByteProvider.js';
6
+ import { parseWorkspaceQualifiedReference } from './workspaceQualifiedReference.js';
5
7
  import { qualifyHandoffWorkspaceTarget } from './workspaceTargetConformance.js';
6
8
  import { inspectRecipientV2Artifact, parseRecipientV2ExternalPayload, parseRecipientV2Facts } from './recipientV2.artifacts.js';
7
9
  import { RECIPIENT_V2_READ_PATH } from './recipientV2.topology.js';
@@ -16,6 +18,7 @@ import { parseHandoffPackageV1, validatePackageFields, WORKSPACE_PACKAGE_ROLE }
16
18
  import { deriveVisibleFacts, validateRouteClosure } from './recipientV2.packageV1.inspect.helpers.js';
17
19
  import { workspaceCarrierProjection } from './recipientV2.packageV1.workspaceProjection.js';
18
20
  import { byteEqual, currentSchemaId, decodeUtf8, dedupeFindings, deepFreeze, oneFile } from './recipientV2.packageV1.shared.js';
21
+ import { parseTransportEnvelopeV1, qualifyTransportEnvelopeV1Artifact, TRANSPORT_ENVELOPE_V1_ROLE, TRANSPORT_ENVELOPE_V1_SCHEMA_ID } from './transportEnvelopeV1.js';
19
22
 
20
23
  export function inspectRecipientFacingV2PackageV1(bundle = {}, options = {}) {
21
24
  const files = Array.isArray(bundle.files) ? bundle.files : [];
@@ -56,13 +59,39 @@ export function inspectRecipientFacingV2PackageV1(bundle = {}, options = {}) {
56
59
  const workspaceParts = [];
57
60
  const workspaceDescriptors = [];
58
61
  const virtualWorkspaceTargets = [];
62
+ const sealedWorkspaceBindings = [];
59
63
  for (const binding of bindings) {
60
64
  const workspaceFile = oneFile(index, binding.workspaceArtifactPath);
61
- const archiveFile = oneFile(index, binding.snapshotPath);
62
- if (!workspaceFile || !archiveFile) { findings.push(finding('error', 'portable.handoff-package-v1.workspace-binding-unresolved', 'Workspace binding paths must resolve exactly once inside the package.', { workspaceId: binding.workspaceId })); continue; }
63
- const workspaceMarkdown = decodeUtf8(packageFileBytes(workspaceFile));
65
+ if (!workspaceFile) { findings.push(finding('error', 'portable.handoff-package-v1.workspace-binding-unresolved', 'Workspace Artifact path must resolve exactly once inside the package.', { workspaceId: binding.workspaceId })); continue; }
66
+ const workspaceData = packageFileByteView(workspaceFile);
67
+ const workspaceMarkdown = decodeUtf8(workspaceData);
64
68
  if (currentSchemaId(workspaceMarkdown) !== 'tiinex.workspace.v1') findings.push(finding('error', 'portable.handoff-package-v1.workspace-schema-invalid', 'Workspace Artifact must declare tiinex.workspace.v1.', { workspaceId: binding.workspaceId, path: workspaceFile.path || '' }));
65
- if (String(binding.snapshotKind || '') !== 'exact-workspace-byte-tree-archive' || String(binding.coverage || '') !== 'complete' || String(binding.bindingState || '') !== 'verified' || String(binding.integrityMethod || '') !== 'sha256') findings.push(finding('error', 'portable.handoff-package-v1.workspace-binding-contract-invalid', 'Qualified package v1 Workspace binding requires exact complete verified sha256 semantics.', { workspaceId: binding.workspaceId }));
69
+
70
+ if (String(binding.snapshotKind || '') === 'password-sealed-workspace-byte-tree') {
71
+ const payloadArtifact = generatedArtifacts.find((item) => item.path === binding.protectedPayloadDescriptorPath && item.schemaId === 'tiinex.external.payload.v1' && item.status === 'qualified') || null;
72
+ const envelopeArtifact = generatedArtifacts.find((item) => item.path === binding.transportEnvelopePath && item.schemaId === TRANSPORT_ENVELOPE_V1_SCHEMA_ID && item.status === 'qualified') || null;
73
+ if (!payloadArtifact || !envelopeArtifact) { findings.push(finding('error', 'portable.handoff-package-v1.workspace-sealed-binding-unresolved', 'Sealed Workspace binding descriptor and Transport Envelope must each resolve exactly once and qualify.', { workspaceId: binding.workspaceId })); continue; }
74
+ const payload = parseRecipientV2ExternalPayload(payloadArtifact.markdown);
75
+ const payloadFile = oneFile(index, payload.location);
76
+ if (!payloadFile || payload.integrityMethod !== 'sha256' || !/^[0-9a-f]{64}$/.test(String(payload.integrityValue || ''))) { findings.push(finding('error', 'portable.handoff-package-v1.workspace-sealed-payload-invalid', 'Protected External Payload descriptor must own one exact package-local ciphertext with SHA-256 integrity.', { workspaceId: binding.workspaceId })); continue; }
77
+ const protectedBytes = packageFileByteView(payloadFile);
78
+ if (sha256Hex(protectedBytes) !== payload.integrityValue || Number(payload.bytes || 0) !== protectedBytes.byteLength) findings.push(finding('error', 'portable.handoff-package-v1.workspace-sealed-payload-byte-mismatch', 'Protected payload descriptor byte identity diverges from exact carried ciphertext.', { workspaceId: binding.workspaceId }));
79
+ if (binding.byteSize !== null && Number(binding.byteSize) !== protectedBytes.byteLength) findings.push(finding('error', 'portable.handoff-package-v1.workspace-sealed-size-mismatch', 'Sealed binding Byte Size diverges from protected payload bytes.', { workspaceId: binding.workspaceId }));
80
+ const envelopeQualification = qualifyTransportEnvelopeV1Artifact(envelopeArtifact.markdown);
81
+ if (envelopeQualification.status !== 'qualified') findings.push(finding('error', 'portable.handoff-package-v1.workspace-sealed-envelope-invalid', 'Transport Envelope failed Secure Transport V1 qualification.', { workspaceId: binding.workspaceId, causes: envelopeQualification.findings || [] }));
82
+ const envelope = envelopeQualification.parsed || parseTransportEnvelopeV1(envelopeArtifact.markdown);
83
+ if (String(envelope.workspaceArtifactPath || '') !== String(binding.workspaceArtifactPath || '') || String(envelope.protectedPayloadDescriptorPath || '') !== String(binding.protectedPayloadDescriptorPath || '')) findings.push(finding('error', 'portable.handoff-package-v1.workspace-sealed-envelope-binding-mismatch', 'Transport Envelope must bind the same visible Workspace Artifact and protected External Payload descriptor as the package binding.', { workspaceId: binding.workspaceId }));
84
+ if (String(envelope.workspaceBindingValue || '') !== sha256Hex(workspaceData)) findings.push(finding('error', 'portable.handoff-package-v1.workspace-sealed-workspace-binding-mismatch', 'Transport Envelope Workspace Binding Value must equal SHA-256 of exact visible Workspace Artifact bytes.', { workspaceId: binding.workspaceId }));
85
+ if (String(payloadArtifact.facts?.workspaceId || '') && String(payloadArtifact.facts.workspaceId) !== String(binding.workspaceId || '')) findings.push(finding('error', 'portable.handoff-package-v1.workspace-sealed-payload-workspace-mismatch', 'Protected payload descriptor Workspace Id diverges from package binding.', { workspaceId: binding.workspaceId }));
86
+ workspaceParts.push({ workspaceId: binding.workspaceId, bindingState: 'sealed', artifact: Object.freeze({ path: workspaceFile.path, sha256: sha256Hex(workspaceData), markdown: workspaceMarkdown }), facts: { workspaceId: binding.workspaceId, sourceWorkspaceTargetInnerPath: '', sourceWorkspaceTargetSha256: sha256Hex(workspaceData) }, archiveFile: null, archive: null, targetQualification: null, protectedPayloadArtifact: payloadArtifact, protectedPayloadFile: payloadFile, transportEnvelopeArtifact: envelopeArtifact });
87
+ sealedWorkspaceBindings.push(Object.freeze({ workspaceId: binding.workspaceId, workspaceArtifactPath: binding.workspaceArtifactPath, workspaceArtifactSha256: sha256Hex(workspaceData), protectedPayloadDescriptorPath: binding.protectedPayloadDescriptorPath, protectedPayloadPath: payload.location, protectedPayloadSha256: payload.integrityValue, protectedPayloadBytes: protectedBytes.byteLength, transportEnvelopePath: binding.transportEnvelopePath, envelope }));
88
+ virtualWorkspaceTargets.push(workspaceFile);
89
+ continue;
90
+ }
91
+
92
+ const archiveFile = oneFile(index, binding.snapshotPath);
93
+ if (!archiveFile) { findings.push(finding('error', 'portable.handoff-package-v1.workspace-binding-unresolved', 'Clear Workspace Snapshot path must resolve exactly once inside the package.', { workspaceId: binding.workspaceId })); continue; }
94
+ if (String(binding.snapshotKind || '') !== 'exact-workspace-byte-tree-archive' || String(binding.coverage || '') !== 'complete' || String(binding.bindingState || '') !== 'verified' || String(binding.integrityMethod || '') !== 'sha256') findings.push(finding('error', 'portable.handoff-package-v1.workspace-binding-contract-invalid', 'Qualified clear package v1 Workspace binding requires exact complete verified sha256 semantics.', { workspaceId: binding.workspaceId }));
66
95
  const archiveData = packageFileByteView(archiveFile);
67
96
  const archiveSha = sha256Hex(archiveData);
68
97
  if (archiveSha !== binding.integrityValue) findings.push(finding('error', 'portable.handoff-package-v1.workspace-snapshot-digest-mismatch', 'Workspace Snapshot Integrity Value diverges from exact carried bytes.', { workspaceId: binding.workspaceId }));
@@ -71,20 +100,20 @@ export function inspectRecipientFacingV2PackageV1(bundle = {}, options = {}) {
71
100
  if (parsed.state !== 'qualified') { findings.push(finding('error', 'portable.handoff-package-v1.workspace-snapshot-invalid', 'Workspace Snapshot failed safe normalized archive qualification.', { workspaceId: binding.workspaceId })); continue; }
72
101
  const inner = (parsed.entries || []).filter((entry) => entry.path === binding.workspaceArtifactInnerPath);
73
102
  if (inner.length !== 1) { findings.push(finding('error', 'portable.handoff-package-v1.workspace-inner-unresolved', 'Workspace Artifact Inner Path must resolve exactly once.', { workspaceId: binding.workspaceId, count: inner.length })); continue; }
74
- const workspaceData = packageFileByteView(workspaceFile);
75
103
  const innerData = packageFileByteView({ data: inner[0].data });
76
104
  if (!byteEqual(workspaceData, innerData)) findings.push(finding('error', 'portable.handoff-package-v1.workspace-inner-byte-mismatch', 'Carried Workspace Artifact bytes must exactly equal the selected snapshot inner entry.', { workspaceId: binding.workspaceId }));
77
105
  const targetQualification = qualifyHandoffWorkspaceTarget({ targetPath: binding.workspaceArtifactInnerPath, targetData: workspaceData, entries: parsed.entries || [] });
78
106
  if (targetQualification.state !== 'qualified') findings.push(finding('error', 'portable.handoff-package-v1.workspace-target-unqualified', 'Exact bound Workspace Artifact does not qualify.', { workspaceId: binding.workspaceId, reasons: targetQualification.reasons || [] }));
79
107
  const descriptorPart = recipientWorkspaceDescriptor({ workspaceId: binding.workspaceId, facts: { providerKind: 'package-local-stored-zip-v1' }, representation: { workspaceArtifactInnerPath: binding.workspaceArtifactInnerPath, coverage: 'complete' }, payload: { location: binding.snapshotPath }, entries: parsed.entries || [], targetMarkdown: workspaceMarkdown, targetPackagePath: binding.workspaceArtifactPath, targetFile: { bytes: workspaceData.byteLength, sha256: sha256Hex(workspaceData) }, archiveFile: { path: archiveFile.path, bytes: archiveData.byteLength, sha256: archiveSha } });
80
108
  workspaceDescriptors.push(descriptorPart);
81
- workspaceParts.push({ workspaceId: binding.workspaceId, artifact: Object.freeze({ path: workspaceFile.path, sha256: sha256Hex(workspaceData), markdown: workspaceMarkdown }), facts: { workspaceId: binding.workspaceId, sourceWorkspaceTargetInnerPath: binding.workspaceArtifactInnerPath, sourceWorkspaceTargetSha256: sha256Hex(workspaceData) }, archiveFile, archive: { archive: parsed, sha256: archiveSha }, targetQualification });
109
+ workspaceParts.push({ workspaceId: binding.workspaceId, bindingState: 'verified', artifact: Object.freeze({ path: workspaceFile.path, sha256: sha256Hex(workspaceData), markdown: workspaceMarkdown }), facts: { workspaceId: binding.workspaceId, sourceWorkspaceTargetInnerPath: binding.workspaceArtifactInnerPath, sourceWorkspaceTargetSha256: sha256Hex(workspaceData) }, archiveFile, archive: { archive: parsed, sha256: archiveSha }, targetQualification });
82
110
  virtualWorkspaceTargets.push(workspaceFile);
83
111
  }
84
112
 
85
113
  const payloadArtifacts = generatedArtifacts.filter((item) => item.schemaId === 'tiinex.external.payload.v1' && item.status === 'qualified');
86
114
  const bootstrapArtifacts = payloadArtifacts.filter((item) => item.facts?.role === 'portable Tooling bootstrap runtime for recipient orientation and verification');
87
115
  const cacheArtifacts = payloadArtifacts.filter((item) => item.facts?.role === 'workspace-scoped Handoff dependency cache');
116
+ const protectedWorkspacePayloadArtifacts = payloadArtifacts.filter((item) => item.facts?.role === 'password-sealed Workspace protected payload');
88
117
  const forbiddenWorkspacePayloads = payloadArtifacts.filter((item) => item.facts?.role === 'workspace-representation-payload');
89
118
  if (forbiddenWorkspacePayloads.length) findings.push(finding('error', 'portable.handoff-package-v1.workspace-payload-redundant', 'Complete package-local Workspace bindings must not carry redundant Workspace External Payload companions.', { count: forbiddenWorkspacePayloads.length }));
90
119
  if (generatedArtifacts.some((item) => item.schemaId === 'tiinex.workspace.representation.v1' || item.schemaId === 'tiinex.relation.v1')) findings.push(finding('error', 'portable.handoff-package-v1.workspace-representation-redundant', 'Complete package-local Workspace bindings must not carry redundant Workspace Representation/Relation companions.'));
@@ -142,14 +171,21 @@ export function inspectRecipientFacingV2PackageV1(bundle = {}, options = {}) {
142
171
  inspectRoutePointers(routePointers, carrierProjection, workspaceParts, endpointRolePointers, participantRolePointers, index, findings);
143
172
  validateRouteClosure(routePointers, endpointRolePointers, participantRolePointers, caches, workspaceParts, findings);
144
173
  if (routePointers.length !== 1) findings.push(finding('error', 'portable.handoff-package-v1.route-count-invalid', 'Qualified Handoff-carrier package-v1 delivery requires exactly one selected Handoff Pointer.', { count: routePointers.length }));
174
+ const sealedWorkspaceIds = new Set(sealedWorkspaceBindings.map((item) => String(item.workspaceId || '')));
145
175
  for (const route of carrierProjection.routes || []) {
146
- if (route.requiredClosure?.state !== 'qualified') findings.push(finding('error', 'portable.handoff-package-v1.required-closure-unqualified', 'Authoritative Handoff Required Context is not fully carried by exact Workspace snapshots or bounded cache.', { routeId: route.id || '' }));
176
+ const unresolved = (route.requiredClosure?.requirements || []).filter((requirement) => requirement.state !== 'qualified');
177
+ const lockedOnly = unresolved.length > 0 && unresolved.every((requirement) => {
178
+ const qualified = parseWorkspaceQualifiedReference(String(requirement.referenceTarget || ''));
179
+ return qualified && sealedWorkspaceIds.has(String(qualified.workspaceId || ''));
180
+ });
181
+ if (route.requiredClosure?.state !== 'qualified' && !lockedOnly) findings.push(finding('error', 'portable.handoff-package-v1.required-closure-unqualified', 'Authoritative Handoff Required Context is neither qualified clear material nor an explicitly sealed locked Workspace binding.', { routeId: route.id || '' }));
147
182
  for (const requirement of route.requiredClosure?.requirements || []) if (requirement.state === 'qualified' && !['workspace-archive-entry', 'materialized-required-material'].includes(String(requirement.resolution?.kind || ''))) findings.push(finding('error', 'portable.handoff-package-v1.external-closure-asset', 'Selected route closure requires a carrier kind not owned by complete Workspace snapshots or bounded cache.', { routeId: route.id || '', requirementId: requirement.requirementId || '', kind: requirement.resolution?.kind || '' }));
148
183
  }
149
184
  const allowedCacheRequirementIds = new Set();
150
185
  for (const route of carrierProjection.routes || []) for (const requirement of route.requiredClosure?.requirements || []) if (requirement.requirementId) allowedCacheRequirementIds.add(String(requirement.requirementId));
151
186
  for (const pointer of endpointRolePointers) if (pointer.facts?.endpointRequirementId) allowedCacheRequirementIds.add(String(pointer.facts.endpointRequirementId));
152
187
  for (const pointer of participantRolePointers) if (pointer.facts?.participantRequirementId) allowedCacheRequirementIds.add(String(pointer.facts.participantRequirementId));
188
+ for (const requirementId of qualifyParentBoundaryCacheRequirementIds(caches, workspaceByteProvider, carrierProjection, findings)) allowedCacheRequirementIds.add(requirementId);
153
189
  for (const cache of caches) for (const material of cache.facts?.materials || []) {
154
190
  const requirementId = String(material.sourceRequirementId || material.requirementId || '');
155
191
  if (!requirementId || !allowedCacheRequirementIds.has(requirementId)) findings.push(finding('error', 'portable.handoff-package-v1.cache-over-expansion', 'Workspace dependency cache contains material not required by the selected Handoff route closure.', { cache: cache.artifact.path, requirementId }));
@@ -159,7 +195,8 @@ export function inspectRecipientFacingV2PackageV1(bundle = {}, options = {}) {
159
195
  const knownPaths = new Set([
160
196
  packageFile?.path,
161
197
  packageContract?.startPath,
162
- ...(bindings || []).flatMap((binding) => [binding.workspaceArtifactPath, binding.snapshotPath]),
198
+ ...(bindings || []).flatMap((binding) => [binding.workspaceArtifactPath, binding.snapshotPath, binding.protectedPayloadDescriptorPath, binding.transportEnvelopePath]),
199
+ ...protectedWorkspacePayloadArtifacts.flatMap((artifact) => [artifact.path, parseRecipientV2ExternalPayload(artifact.markdown).location]),
163
200
  ...generatedArtifacts.filter((item) => ['recovery-orientation', 'handoff-route', 'endpoint-role', 'participant-role'].includes(String(item.facts?.role || ''))).map((item) => item.path),
164
201
  ...bootstrapArtifacts.flatMap((artifact) => [artifact.path, parseRecipientV2ExternalPayload(artifact.markdown).location]),
165
202
  ...caches.flatMap((cache) => [cache.artifact.path, cache.file.path])
@@ -175,7 +212,7 @@ export function inspectRecipientFacingV2PackageV1(bundle = {}, options = {}) {
175
212
  return deepFreeze({
176
213
  schema: 'tiinex.portable.recipient-facing-handoff-package-v1.inspection.v1', detected: Boolean(packageFile), status, format: RECIPIENT_V2_PACKAGE_V1_FORMAT_ID,
177
214
  rootArtifact: packageFile ? Object.freeze({ path: packageFile.path, schemaId: RECIPIENT_V2_PACKAGE_V1_SCHEMA_ID, sha256: sha256Hex(packageFileBytes(packageFile)), carrierLineage: lineage }) : null,
178
- readArtifact, workspaces: Object.freeze(workspaceParts.map((item) => Object.freeze({ workspaceId: item.workspaceId, coverage: 'complete', workspaceArtifactPath: item.artifact.path, workspaceArchivePath: item.archiveFile.path, sourceWorkspaceTargetInnerPath: item.facts.sourceWorkspaceTargetInnerPath, sourceWorkspaceTargetSha256: item.facts.sourceWorkspaceTargetSha256 }))),
215
+ readArtifact, workspaces: Object.freeze(workspaceParts.map((item) => Object.freeze({ workspaceId: item.workspaceId, coverage: 'complete', bindingState: item.bindingState || 'verified', workspaceArtifactPath: item.artifact.path, workspaceArchivePath: item.archiveFile?.path || '', sourceWorkspaceTargetInnerPath: item.facts.sourceWorkspaceTargetInnerPath, sourceWorkspaceTargetSha256: item.facts.sourceWorkspaceTargetSha256 }))), sealedWorkspaces: Object.freeze(sealedWorkspaceBindings),
179
216
  routes: projectRecipientV2Routes(routePointers, endpointRolePointers, participantRolePointers), endpointRoles: projectRecipientV2EndpointRoles(endpointRolePointers), participantRoles: projectRecipientV2ParticipantRoles(participantRolePointers),
180
217
  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 || [] }))),
181
218
  bootstrapInspection, transportManifest: null, artifactFacts: Object.freeze(generatedArtifacts.map((item) => Object.freeze({ path: item.path, facts: item.facts }))), descriptor, workspaceByteProvider, carrierProjection, coldConsumerProjection,
@@ -183,3 +220,78 @@ export function inspectRecipientFacingV2PackageV1(bundle = {}, options = {}) {
183
220
  boundary: 'Read-only qualification of tiinex.handoff.package.v1: visible package identity/discovery and complete package-local Workspace bindings are reverified from exact bytes; derived inventories have no authority.'
184
221
  });
185
222
  }
223
+
224
+ function qualifyParentBoundaryCacheRequirementIds(caches = [], workspaceByteProvider = {}, carrierProjection = {}, findings = []) {
225
+ const parentMaterials = [];
226
+ const targetIndex = new Map();
227
+ for (const cache of caches) for (const item of cache.facts?.materials || []) {
228
+ if (String(item.classification || '') !== 'parent-boundary') continue;
229
+ const entry = (cache.archive?.archive?.entries || []).find((candidate) => String(candidate.path || '') === String(item.archiveEntry || '')) || null;
230
+ const normalized = Object.freeze({ cache, item, entry, targetKey: `${String(item.targetWorkspaceId || '')}\0${normalizeWorkspacePath(item.targetPath || item.originalPath || '')}` });
231
+ parentMaterials.push(normalized);
232
+ const list = targetIndex.get(normalized.targetKey) || [];
233
+ list.push(normalized);
234
+ targetIndex.set(normalized.targetKey, list);
235
+ }
236
+ const allowed = new Set();
237
+ for (const candidate of parentMaterials) {
238
+ const item = candidate.item || {};
239
+ const requirementId = String(item.requirementId || '');
240
+ const routeWorkspaceId = String(item.routeWorkspaceId || '');
241
+ const routePath = normalizeWorkspacePath(item.routePath || '');
242
+ const sourceWorkspaceId = String(item.sourceWorkspaceId || '');
243
+ const sourcePath = normalizeWorkspacePath(item.sourcePath || '');
244
+ const targetWorkspaceId = String(item.targetWorkspaceId || '');
245
+ const targetPath = normalizeWorkspacePath(item.targetPath || item.originalPath || '');
246
+ const referenceTarget = String(item.referenceTarget || '').trim();
247
+ const routeQualified = (carrierProjection.routes || []).some((route) => String(route.state || '') === 'qualified' && String(route.workspaceId || '') === routeWorkspaceId && normalizeWorkspacePath(route.workspaceRelativePath || '') === routePath);
248
+ let reason = '';
249
+ if (!requirementId || !routeQualified || !sourceWorkspaceId || !sourcePath || !targetWorkspaceId || !targetPath || !referenceTarget || !candidate.entry) reason = 'parent-boundary-visible-facts-incomplete';
250
+ if (!reason && targetIndex.get(candidate.targetKey)?.length !== 1) reason = 'parent-boundary-target-ambiguous';
251
+ if (!reason) {
252
+ const alreadyCarried = resolveHandoffWorkspaceEntry(workspaceByteProvider, targetWorkspaceId, targetPath);
253
+ if (alreadyCarried.state === 'qualified') reason = 'parent-boundary-target-redundantly-carried';
254
+ }
255
+ let sourceData = null;
256
+ if (!reason) {
257
+ const sourceCarried = resolveHandoffWorkspaceEntry(workspaceByteProvider, sourceWorkspaceId, sourcePath);
258
+ if (sourceCarried.state === 'qualified') sourceData = sourceCarried.data;
259
+ else {
260
+ const sourceCandidates = targetIndex.get(`${sourceWorkspaceId}\0${sourcePath}`) || [];
261
+ if (sourceCandidates.length === 1 && sourceCandidates[0].entry) sourceData = sourceCandidates[0].entry.data;
262
+ else reason = 'parent-boundary-source-unresolved';
263
+ }
264
+ }
265
+ if (!reason) {
266
+ const markdown = decodeUtf8(packageFileBytes({ data: sourceData }));
267
+ let parent = null;
268
+ try { parent = parseArtifactMarkdown(markdown).envelope?.parent || null; } catch { parent = null; }
269
+ const declared = String(parent?.trace || (parent?.originEntries || []).find((entry) => String(entry?.label || '').trim() === 'relative')?.target || '').trim();
270
+ if (!declared || declared !== referenceTarget) reason = 'parent-boundary-reference-mismatch';
271
+ else {
272
+ const qualified = parseWorkspaceQualifiedReference(declared);
273
+ const expectedWorkspaceId = qualified ? String(qualified.workspaceId || '') : sourceWorkspaceId;
274
+ const expectedPath = qualified ? normalizeWorkspacePath(qualified.path || '') : resolveRelativeWorkspacePath(sourcePath, declared);
275
+ if (!expectedWorkspaceId || !expectedPath || expectedWorkspaceId !== targetWorkspaceId || expectedPath !== targetPath) reason = 'parent-boundary-target-mismatch';
276
+ }
277
+ }
278
+ if (reason) findings.push(finding('error', 'portable.handoff-package-v1.cache-parent-boundary-unqualified', 'Workspace dependency cache Parent-boundary material is not independently justified by the selected route declared Parent chain.', { requirementId, reason }));
279
+ else allowed.add(requirementId);
280
+ }
281
+ return allowed;
282
+ }
283
+
284
+ function resolveRelativeWorkspacePath(sourcePath = '', target = '') {
285
+ let raw;
286
+ try { raw = decodeURIComponent(String(target || '').split('#')[0].split('?')[0]); } catch { return ''; }
287
+ if (!raw || raw.startsWith('/') || raw.startsWith('\\') || /^[a-z][a-z0-9+.-]*:/i.test(raw) || raw.startsWith('//')) return '';
288
+ const parts = normalizeWorkspacePath(sourcePath).split('/').slice(0, -1);
289
+ for (const part of raw.replace(/\\/g, '/').split('/')) {
290
+ if (!part || part === '.') continue;
291
+ if (part === '..') { if (!parts.length) return ''; parts.pop(); }
292
+ else parts.push(part);
293
+ }
294
+ return normalizeWorkspacePath(parts.join('/'));
295
+ }
296
+
297
+ function normalizeWorkspacePath(value = '') { return String(value || '').trim().replace(/\\/g, '/').replace(/^\.\//, '').replace(/^\/+/, '').split('/').filter((part) => part && part !== '.').join('/'); }
@@ -3,8 +3,9 @@ import { RECIPIENT_V2_PACKAGE_V1_SCHEMA_ID } from './recipientV2.packageV1.const
3
3
  import { currentSchemaId, decodeUtf8 } from './recipientV2.packageV1.shared.js';
4
4
 
5
5
  export { 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';
6
- export { buildRecipientFacingV2PackageV1 } from './recipientV2.packageV1.build.js';
6
+ export { buildRecipientFacingV2PackageV1, buildRecipientFacingV2PackageV1Secure } from './recipientV2.packageV1.build.js';
7
7
  export { inspectRecipientFacingV2PackageV1 } from './recipientV2.packageV1.inspect.js';
8
+ export { inspectRecipientV2PackageV1SealedBinding, openRecipientV2PackageV1SealedWorkspace, replaceRecipientV2PackageV1SealedWorkspaceRecipients } from './recipientV2.packageV1.secure.js';
8
9
  export { parseHandoffPackageV1, renderHandoffPackageV1 } from './recipientV2.packageV1.contract.js';
9
10
 
10
11
  export function isRecipientV2PackageV1Surface(files = []) {
@@ -0,0 +1,87 @@
1
+ import { packageFileByteView, packageFileBytes, sha256Hex } from '../../../export/package.bytes.js';
2
+ import { openPasswordWorkspacePayload, replacePasswordWorkspaceRecipients } from '../../../transport/secureTransportV1.js';
3
+ import { buildHandoffWorkspaceByteProvider, inspectStoredWorkspaceArchive } from './workspaceByteProvider.js';
4
+ import { qualifyHandoffWorkspaceTarget } from './workspaceTargetConformance.js';
5
+ import { indexRecipientFiles, recipientWorkspaceDescriptor } from './recipientV2.inspect.helpers.js';
6
+ import { parseRecipientV2ExternalPayload } from './recipientV2.artifacts.js';
7
+ import { inspectRecipientFacingV2PackageV1 } from './recipientV2.packageV1.inspect.js';
8
+ import { parseHandoffPackageV1 } from './recipientV2.packageV1.contract.js';
9
+ import { parseTransportEnvelopeV1 } from './transportEnvelopeV1.js';
10
+ import { currentSchemaId, decodeUtf8, oneFile } from './recipientV2.packageV1.shared.js';
11
+
12
+ export async function openRecipientV2PackageV1SealedWorkspace(bundle = {}, input = {}) {
13
+ const inspection = inspectRecipientFacingV2PackageV1(bundle);
14
+ if (inspection.status !== 'valid') return frozen({ state: 'failed', reason: 'package-unqualified', findings: inspection.findings || [] });
15
+ const workspaceId = String(input.workspaceId || '').trim();
16
+ const sealed = (inspection.sealedWorkspaces || []).filter((item) => String(item.workspaceId || '') === workspaceId);
17
+ if (sealed.length !== 1) return frozen({ state: 'locked', reason: sealed.length > 1 ? 'sealed-workspace-ambiguous' : 'sealed-workspace-unresolved' });
18
+ const target = sealed[0];
19
+ const files = Array.isArray(bundle.files) ? bundle.files : [];
20
+ const index = indexRecipientFiles(files, []);
21
+ const workspaceFile = oneFile(index, target.workspaceArtifactPath);
22
+ const payloadFile = oneFile(index, target.protectedPayloadPath);
23
+ const envelopeFile = oneFile(index, target.transportEnvelopePath);
24
+ if (!workspaceFile || !payloadFile || !envelopeFile) return frozen({ state: 'failed', reason: 'sealed-carrier-material-unresolved' });
25
+ const envelope = parseTransportEnvelopeV1(decodeUtf8(packageFileBytes(envelopeFile)));
26
+ const opened = await openPasswordWorkspacePayload({ protectedPayload: packageFileByteView(payloadFile), envelope, password: input.password, slotId: input.slotId, crypto: input.crypto });
27
+ if (opened.state !== 'opened') return opened;
28
+
29
+ const parsed = inspectStoredWorkspaceArchive(opened.plaintext, { ownedBytes: true });
30
+ if (parsed.state !== 'qualified') return frozen({ state: 'failed', reason: 'recovered-archive-unqualified', findings: parsed.findings || [] });
31
+ const workspaceBytes = packageFileByteView(workspaceFile);
32
+ const matches = (parsed.entries || []).filter((entry) => byteEqual(entry.data, workspaceBytes));
33
+ if (matches.length !== 1) return frozen({ state: 'failed', reason: matches.length > 1 ? 'workspace-artifact-correlation-ambiguous' : 'workspace-artifact-correlation-unresolved', matchCount: matches.length });
34
+ const innerPath = String(matches[0].path || '');
35
+ const targetQualification = qualifyHandoffWorkspaceTarget({ targetPath: innerPath, targetData: workspaceBytes, entries: parsed.entries || [] });
36
+ if (targetQualification.state !== 'qualified') return frozen({ state: 'failed', reason: 'recovered-workspace-unqualified', findings: targetQualification.reasons || [] });
37
+
38
+ const transientArchivePath = `tiinex-transient-opened-${safeToken(workspaceId)}.workspace.zip`;
39
+ const transientArchiveFile = frozen({ path: transientArchivePath, kind: 'transient-opened-workspace-archive', mediaType: 'application/zip', data: opened.plaintext, bytes: opened.plaintext.byteLength, sha256: sha256Hex(opened.plaintext) });
40
+ const descriptorPart = recipientWorkspaceDescriptor({
41
+ workspaceId,
42
+ facts: { providerKind: 'package-local-stored-zip-v1' },
43
+ representation: { workspaceArtifactInnerPath: innerPath, coverage: 'complete' },
44
+ payload: { location: transientArchivePath },
45
+ entries: parsed.entries || [],
46
+ targetMarkdown: decodeUtf8(workspaceBytes),
47
+ targetPackagePath: target.workspaceArtifactPath,
48
+ targetFile: { bytes: workspaceBytes.byteLength, sha256: sha256Hex(workspaceBytes) },
49
+ archiveFile: { path: transientArchivePath, bytes: opened.plaintext.byteLength, sha256: sha256Hex(opened.plaintext) }
50
+ });
51
+ const descriptor = frozen({ schema: 'tiinex.transport.handoff-material-closure-descriptor.v2', version: 2, workspaceMaterializations: [descriptorPart.workspace], workspaceArchiveBindings: [descriptorPart.binding], materialized: [], requirements: { required: [], reference: [], endpointRoles: [], participantRoles: [], dependencies: [] } });
52
+ const provider = buildHandoffWorkspaceByteProvider({ ...bundle, files: frozen([...files, transientArchiveFile]) }, descriptor);
53
+ if (provider.status !== 'ready') return frozen({ state: 'failed', reason: 'recovered-provider-unqualified', findings: provider.findings || [] });
54
+ return frozen({
55
+ state: 'opened-qualified', workspaceId, slotId: opened.slotId, workspaceArtifactPath: target.workspaceArtifactPath,
56
+ workspaceArtifactInnerPath: innerPath, workspaceArtifactSha256: sha256Hex(workspaceBytes), archiveSha256: sha256Hex(opened.plaintext),
57
+ provider, descriptor, targetQualification,
58
+ boundary: 'Authorized plaintext exists only in this in-memory opened provider/descriptor result unless the caller explicitly writes a destination. Decryption alone did not activate the provider; safe archive, unique Workspace-byte correlation, ordinary Workspace conformance, and integrity qualification all completed first.'
59
+ });
60
+ }
61
+
62
+ export async function replaceRecipientV2PackageV1SealedWorkspaceRecipients(bundle = {}, input = {}) {
63
+ const inspection = inspectRecipientFacingV2PackageV1(bundle);
64
+ if (inspection.status !== 'valid') return frozen({ state: 'failed', reason: 'package-unqualified', findings: inspection.findings || [] });
65
+ const workspaceId = String(input.workspaceId || '').trim();
66
+ const target = (inspection.sealedWorkspaces || []).find((item) => String(item.workspaceId || '') === workspaceId) || null;
67
+ if (!target) return frozen({ state: 'locked', reason: 'sealed-workspace-unresolved' });
68
+ const index = indexRecipientFiles(bundle.files || [], []);
69
+ const payloadFile = oneFile(index, target.protectedPayloadPath);
70
+ const envelopeFile = oneFile(index, target.transportEnvelopePath);
71
+ if (!payloadFile || !envelopeFile) return frozen({ state: 'failed', reason: 'sealed-carrier-material-unresolved' });
72
+ const envelope = parseTransportEnvelopeV1(decodeUtf8(packageFileBytes(envelopeFile)));
73
+ const changed = await replacePasswordWorkspaceRecipients({ protectedPayload: packageFileByteView(payloadFile), envelope, authorizationPassword: input.authorizationPassword ?? input.password, recipients: input.recipients || [], crypto: input.crypto });
74
+ if (changed.state !== 'rewrapped') return changed;
75
+ return frozen({ state: 'rewrapped', protectedPayload: changed.protectedPayload, envelope: changed.envelope, workspaceId, protectedPayloadSha256: sha256Hex(changed.protectedPayload), boundary: 'Returns replacement non-secret envelope metadata and the exact unchanged protected payload bytes; it does not rewrite the package automatically because parent/self-integrity resealing remains a separate carrier manufacture action.' });
76
+ }
77
+
78
+ export function inspectRecipientV2PackageV1SealedBinding(bundle = {}, workspaceId = '') {
79
+ const inspection = inspectRecipientFacingV2PackageV1(bundle);
80
+ const matches = (inspection.sealedWorkspaces || []).filter((item) => String(item.workspaceId || '') === String(workspaceId || ''));
81
+ return frozen({ state: inspection.status !== 'valid' ? 'failed' : matches.length === 1 ? 'locked-qualified' : matches.length > 1 ? 'ambiguous' : 'unresolved', binding: matches.length === 1 ? matches[0] : null, providerActive: false, packageStatus: inspection.status, findings: inspection.findings || [] });
82
+ }
83
+
84
+ function byteEqual(a, b) { const left=packageFileByteView({data:a}), right=packageFileByteView({data:b}); if(left.byteLength!==right.byteLength)return false; let diff=0; for(let i=0;i<left.byteLength;i+=1) diff|=left[i]^right[i]; return diff===0; }
85
+ function safeToken(value=''){return String(value||'').trim().toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-+|-+$/g,'').slice(0,80)||'workspace';}
86
+ function frozen(value){return deepFreeze(value);}
87
+ 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);}
@@ -12,7 +12,15 @@ export function oneFile(index, path) { const list = index.get(String(path || '')
12
12
  export function validCarrierDimension(value = '') { return /^\d{3}(?:-(?:[1-9]\d*))*$/.test(String(value || '')); }
13
13
  export function numericDimension(path = '') { return String(path || '').match(/^(\d{3}(?:-[1-9]\d*)*)-/)?.[1] || ''; }
14
14
  export function currentSchemaId(markdown = '') { return String(markdown || '').match(/^\s*-\s+Current Schema:\s*(?:\[)?(tiinex\.[A-Za-z0-9._-]+)(?:\])?/mi)?.[1] || ''; }
15
- export function sectionText(markdown = '', title = '') { const source = String(markdown || ''); const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const match = source.match(new RegExp(`^##\\s+${escaped}\\s*$([\\s\\S]*?)(?=^##\\s+|^#\\s+Continuity Integrity\\s*$|\\Z)`, 'mi')); return match ? match[1] : ''; }
15
+ export function sectionText(markdown = '', title = '') {
16
+ const source = String(markdown || '');
17
+ const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
18
+ const heading = new RegExp(`^##\\s+${escaped}\\s*$`, 'mi').exec(source);
19
+ if (!heading) return '';
20
+ const rest = source.slice(heading.index + heading[0].length);
21
+ const next = /^(?:##\s+|#\s+Continuity Integrity\s*$)/mi.exec(rest);
22
+ return next ? rest.slice(0, next.index) : rest;
23
+ }
16
24
  export function field(section = '', label = '') { const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); return String(section || '').match(new RegExp(`^\\s*-\\s+${escaped}:\\s*(.*?)\\s*$`, 'mi'))?.[1]?.trim() || ''; }
17
25
  export function markdownTarget(value = '') { const match = String(value || '').match(/\[[^\]]*\]\(([^)]+)\)/); return match ? match[1].trim() : ''; }
18
26
  export function unquote(value = '') { const text = String(value || '').trim(); return text.startsWith('`') && text.endsWith('`') ? text.slice(1, -1) : text; }
@@ -157,9 +157,9 @@ function buildRecipientFacingV2TopologyLegacy(input = {}) {
157
157
  archivePath,
158
158
  archiveBytes: cacheFile.bytes,
159
159
  archiveSha256: cacheFile.sha256,
160
- materials: materials.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 }))
160
+ materials: materials.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 }))
161
161
  };
162
- const cacheArtifact = finalizeFile({ path: artifactPath, kind: 'tiinex-external-payload-artifact', logicalKind: 'recipient-v2-workspace-dependency-cache-reference', mediaType: 'text/markdown', transportFacts: recipientV2TransportFacts('workspace-scoped Handoff dependency cache', cacheFacts), content: renderRecipientV2ExternalPayload({ createdAt, parent: workspace.parent, title: `Workspace Dependency Cache — ${workspace.workspaceId}`, summary: 'Exact recipient-relative dependency bytes not satisfied by any qualified Workspace archive.', label: `${workspace.workspaceId} Handoff dependency cache`, kind: 'zip export', role: 'workspace-scoped Handoff dependency cache', location: archivePath, bytes: cacheFile.bytes, sha256: cacheFile.sha256, facts: cacheFacts }) });
162
+ const cacheArtifact = finalizeFile({ path: artifactPath, kind: 'tiinex-external-payload-artifact', logicalKind: 'recipient-v2-workspace-dependency-cache-reference', mediaType: 'text/markdown', transportFacts: recipientV2TransportFacts('workspace-scoped Handoff dependency cache', cacheFacts), content: renderRecipientV2ExternalPayload({ createdAt, parent: workspace.parent, title: `Workspace Dependency Cache — ${workspace.workspaceId}`, summary: 'Exact recipient-relative dependency bytes not satisfied by any qualified Workspace archive.', label: `${workspace.workspaceId} Handoff dependency cache`, kind: 'zip export', role: 'workspace-scoped Handoff dependency cache', location: archivePath, bytes: cacheFile.bytes, sha256: cacheFile.sha256, materials: cacheFacts.materials }) });
163
163
  files.push(cacheArtifact, cacheFile);
164
164
  const projection = Object.freeze({ workspaceId: workspace.workspaceId, artifactPath, archivePath, materials: cacheFacts.materials });
165
165
  topology.caches.push(projection);
@@ -47,6 +47,13 @@ export function roleMaterialTarget(requirement = {}, descriptor = {}, workspaceB
47
47
 
48
48
  export function detachedMaterial(descriptor, byPath, findings) {
49
49
  const out = [];
50
+ const requirementById = new Map([
51
+ ...(descriptor.requirements?.required || []),
52
+ ...(descriptor.requirements?.reference || []),
53
+ ...(descriptor.requirements?.endpointRoles || []),
54
+ ...(descriptor.requirements?.participantRoles || []),
55
+ ...(descriptor.requirements?.dependencies || [])
56
+ ].map((item) => [String(item.requirementId || ''), item]));
50
57
  for (const material of descriptor.materialized || []) {
51
58
  if (String(material.carrierKind || '') === 'workspace-archive-entry') continue;
52
59
  const file = oneFile(byPath, material.packagePath, findings, 'detached-material');
@@ -54,7 +61,8 @@ export function detachedMaterial(descriptor, byPath, findings) {
54
61
  const data = packageFileBytes(file);
55
62
  const sha256 = sha256Hex(data);
56
63
  if (Number(material.bytes || 0) !== data.byteLength || String(material.sha256 || '') !== sha256) findings.push(finding('error', 'portable.handoff-v2-surface.cache.material-identity-mismatch', 'Detached material bytes diverge from qualified closure identity.', { requirementId: material.requirementId || '' }));
57
- out.push(Object.freeze({ requirementId: String(material.requirementId || ''), classification: String(material.classification || ''), referenceTarget: String(material.referenceTarget || ''), routeWorkspaceId: String(material.routeWorkspaceId || ''), routePath: String(material.routePath || ''), sourceRequirementId: String(material.sourceRequirementId || ''), originalPath: String(material.originalPath || ''), bytes: data.byteLength, sha256, data }));
64
+ const requirement = requirementById.get(String(material.requirementId || '')) || {};
65
+ out.push(Object.freeze({ requirementId: String(material.requirementId || ''), classification: String(material.classification || ''), referenceTarget: String(material.referenceTarget || ''), routeWorkspaceId: String(material.routeWorkspaceId || ''), routePath: String(material.routePath || ''), sourceRequirementId: String(material.sourceRequirementId || ''), sourceWorkspaceId: String(requirement.sourceWorkspaceId || ''), sourcePath: String(requirement.sourcePath || ''), targetWorkspaceId: String(requirement.targetWorkspaceId || material.provenance?.workspaceId || ''), targetPath: String(requirement.targetPath || material.originalPath || ''), originalPath: String(material.originalPath || ''), bytes: data.byteLength, sha256, data }));
58
66
  }
59
67
  return out.sort((a, b) => a.requirementId.localeCompare(b.requirementId));
60
68
  }
@@ -0,0 +1,76 @@
1
+ import { canonicalC14nV2SelfState, sealC14nV2Self } from '../../../integrity/integrity.c14nV2.js';
2
+ import { C14N_V2_VALIDATOR_TARGET } from '../../../integrity/integrity.methodReference.js';
3
+ import { RECIPIENT_V2_ROOT_SCHEMA_TARGET } from './recipientV2.artifacts.js';
4
+ import { field, markdownTarget, sectionText, unquote } from './recipientV2.packageV1.shared.js';
5
+ import { qualifySecureTransportV1Envelope } from '../../../transport/secureTransportV1.js';
6
+
7
+ export const TRANSPORT_ENVELOPE_V1_SCHEMA_ID = 'tiinex.transport.envelope.v1';
8
+ export const TRANSPORT_ENVELOPE_V1_ROLE = 'password-sealed-workspace-transport-envelope';
9
+
10
+ export function renderTransportEnvelopeV1(input = {}) {
11
+ const envelope = input.envelope || {};
12
+ const q = qualifySecureTransportV1Envelope(envelope);
13
+ if (q.state !== 'qualified') throw new Error(`secure-transport.envelope-unqualified:${q.state}`);
14
+ const workspacePath = String(input.workspaceArtifactPath || '').trim();
15
+ const payloadPath = String(input.protectedPayloadDescriptorPath || '').trim();
16
+ if (!workspacePath || !payloadPath) throw new Error('secure-transport.envelope-binding-path-missing');
17
+ const profile = envelope.profile || {};
18
+ const slots = (envelope.passwordRecipientSlots || []).map((slot) => `- ${slot.slotId}\n - Slot Id: ${slot.slotId}\n - Slot Kind: ${slot.slotKind}\n - KDF Algorithm: ${slot.kdfAlgorithm}\n - KDF Salt Encoding: ${slot.kdfSaltEncoding}\n - KDF Salt: ${slot.kdfSalt}\n - KDF Parameters: \`${stableJson(slot.kdfParameters)}\`\n - Key Wrap Algorithm: ${slot.keyWrapAlgorithm}\n - Key Wrap Parameters: \`${stableJson(slot.keyWrapParameters)}\`\n - Wrapped Content Key Encoding: ${slot.wrappedContentKeyEncoding}\n - Wrapped Content Key: ${slot.wrappedContentKey}\n - Slot Verification Rule: ${slot.slotVerificationRule}${slot.recipientHint ? `\n - Recipient Hint: ${slot.recipientHint}` : ''}`).join('\n');
19
+ const createdAt = normalizeCreatedAt(input.createdAt || '');
20
+ const unsigned = `# Continuity Context\n\n- Envelope Schema: [tiinex.root.v1](${RECIPIENT_V2_ROOT_SCHEMA_TARGET})\n${renderParentEnvelope(input.parent)}- Current\n - Current Schema: ${TRANSPORT_ENVELOPE_V1_SCHEMA_ID}\n - Created At: ${createdAt}\n - Summary: Password-sealed Workspace transport envelope with explicit supported profile metadata and independently usable password recipient slots.\n\n---\n\n# Transport Envelope\n\n## Envelope Binding\n\n- Workspace Artifact: [Workspace](${workspacePath})\n- Protected Payload: [Protected Payload](${payloadPath})\n- Envelope Purpose: ${envelope.envelopePurpose}\n- Envelope Version: ${envelope.envelopeVersion}\n- Plaintext Representation Kind: ${envelope.plaintextRepresentationKind}\n- Protected Name Tree: ${envelope.protectedNameTree}\n- Workspace Binding Method: ${envelope.workspaceBindingMethod}\n- Workspace Binding Value: ${envelope.workspaceBindingValue}\n- Content Key Scope: ${envelope.contentKeyScope}\n\n## Payload Protection Profile\n\n- Profile Id: ${profile.profileId}\n- Profile Version: ${profile.profileVersion}\n- Content Encryption Algorithm: ${profile.contentEncryptionAlgorithm}\n- Content Encryption Parameters: \`${stableJson(profile.contentEncryptionParameters)}\`\n- Nonce Or IV Encoding: ${profile.nonceOrIvEncoding}\n- Nonce Or IV: ${profile.nonceOrIv}\n- Payload Authentication Rule: ${profile.payloadAuthenticationRule}\n- Security Metadata Authentication Rule: ${profile.securityMetadataAuthenticationRule}\n- Recipient Change Payload Rule: ${profile.recipientChangePayloadRule}\n\n## Password Recipient Slots\n\n${slots}\n\n## Open And Recovery Contract\n\n- Open Rule: ${envelope.openRule}\n- Wrong Password Result: ${envelope.wrongPasswordResult}\n- Unsupported Profile Result: ${envelope.unsupportedProfileResult}\n- Malformed Metadata Result: ${envelope.malformedMetadataResult}\n- Authentication Failure Result: ${envelope.authenticationFailureResult}\n- Missing Authorized Slot Result: ${envelope.missingAuthorizedSlotResult}\n- Secret Persistence: ${envelope.secretPersistence}\n- Plaintext Persistence: ${envelope.plaintextPersistence}\n- Recovery Rule: ${envelope.recoveryRule}\n- Failure Policy: ${envelope.failurePolicy}\n- Multi-Workspace Isolation: ${envelope.multiWorkspaceIsolation}\n\n## Qualification Boundary\n\n- Payload Byte Integrity Owner: ${envelope.payloadByteIntegrityOwner}\n- Envelope Root Integrity Meaning: ${envelope.envelopeRootIntegrityMeaning}\n- Cryptographic Authentication Meaning: ${envelope.cryptographicAuthenticationMeaning}\n- Provider State While Locked: ${envelope.providerStateWhileLocked}\n- Post-Open Qualification: ${envelope.postOpenQualification}\n- Semantic Authority: ${envelope.semanticAuthority}\n\n## Disclosure Boundary\n\n- Outer Visible Material: ${envelope.outerVisibleMaterial}\n- Must Remain Sealed: ${envelope.mustRemainSealed}\n- Forbidden Durable Secrets: ${envelope.forbiddenDurableSecrets}\n- Privacy Policy Owner: ${envelope.privacyPolicyOwner}\n\n## Interpretation Limits\n\n- Encryption and successful open do not create Workspace identity, Handoff authority, provenance, semantic truth, acceptance, or provider qualification.\n- Passwords, derived keys, wrapping keys, plaintext content keys, and protected Workspace path inventory are not durable envelope material.\n\n---\n\n# Continuity Integrity\n\n${renderParentIntegrity(input.parent)}- [sha256-base64url-c14n-v2](${C14N_V2_VALIDATOR_TARGET})\n - Towards: self\n - Value: pending\n`;
21
+ const sealed = sealC14nV2Self(unsigned);
22
+ if (sealed.state !== 'sealed') throw new Error(`secure-transport.envelope-self-seal-failed:${sealed.reason || sealed.state}`);
23
+ return `${sealed.markdown}\n`;
24
+ }
25
+
26
+ export function parseTransportEnvelopeV1(markdown = '') {
27
+ const body = String(markdown || '');
28
+ const binding = sectionText(body, 'Envelope Binding');
29
+ const profile = sectionText(body, 'Payload Protection Profile');
30
+ const open = sectionText(body, 'Open And Recovery Contract');
31
+ const qualification = sectionText(body, 'Qualification Boundary');
32
+ const disclosure = sectionText(body, 'Disclosure Boundary');
33
+ return deepFreeze({
34
+ workspaceArtifactPath: markdownTarget(field(binding, 'Workspace Artifact')),
35
+ protectedPayloadDescriptorPath: markdownTarget(field(binding, 'Protected Payload')),
36
+ envelopePurpose: field(binding, 'Envelope Purpose'), envelopeVersion: numberField(binding, 'Envelope Version'), plaintextRepresentationKind: field(binding, 'Plaintext Representation Kind'), protectedNameTree: field(binding, 'Protected Name Tree'), workspaceBindingMethod: field(binding, 'Workspace Binding Method'), workspaceBindingValue: field(binding, 'Workspace Binding Value'), contentKeyScope: field(binding, 'Content Key Scope'),
37
+ profile: {
38
+ profileId: field(profile, 'Profile Id'), profileVersion: numberField(profile, 'Profile Version'), contentEncryptionAlgorithm: field(profile, 'Content Encryption Algorithm'), contentEncryptionParameters: jsonObjectField(profile, 'Content Encryption Parameters'), nonceOrIvEncoding: field(profile, 'Nonce Or IV Encoding'), nonceOrIv: field(profile, 'Nonce Or IV'), payloadAuthenticationRule: field(profile, 'Payload Authentication Rule'), securityMetadataAuthenticationRule: field(profile, 'Security Metadata Authentication Rule'), recipientChangePayloadRule: field(profile, 'Recipient Change Payload Rule')
39
+ },
40
+ passwordRecipientSlots: parseSlots(sectionText(body, 'Password Recipient Slots')),
41
+ openRule: field(open, 'Open Rule'), wrongPasswordResult: field(open, 'Wrong Password Result'), unsupportedProfileResult: field(open, 'Unsupported Profile Result'), malformedMetadataResult: field(open, 'Malformed Metadata Result'), authenticationFailureResult: field(open, 'Authentication Failure Result'), missingAuthorizedSlotResult: field(open, 'Missing Authorized Slot Result'), secretPersistence: field(open, 'Secret Persistence'), plaintextPersistence: field(open, 'Plaintext Persistence'), recoveryRule: field(open, 'Recovery Rule'), failurePolicy: field(open, 'Failure Policy'), multiWorkspaceIsolation: field(open, 'Multi-Workspace Isolation'),
42
+ payloadByteIntegrityOwner: field(qualification, 'Payload Byte Integrity Owner'), envelopeRootIntegrityMeaning: field(qualification, 'Envelope Root Integrity Meaning'), cryptographicAuthenticationMeaning: field(qualification, 'Cryptographic Authentication Meaning'), providerStateWhileLocked: field(qualification, 'Provider State While Locked'), postOpenQualification: field(qualification, 'Post-Open Qualification'), semanticAuthority: field(qualification, 'Semantic Authority'),
43
+ outerVisibleMaterial: field(disclosure, 'Outer Visible Material'), mustRemainSealed: field(disclosure, 'Must Remain Sealed'), forbiddenDurableSecrets: field(disclosure, 'Forbidden Durable Secrets'), privacyPolicyOwner: field(disclosure, 'Privacy Policy Owner')
44
+ });
45
+ }
46
+
47
+ export function qualifyTransportEnvelopeV1Artifact(markdown = '') {
48
+ const parsed = parseTransportEnvelopeV1(markdown);
49
+ const findings = [];
50
+ const self = canonicalC14nV2SelfState(String(markdown || ''));
51
+ if (self.state !== 'verified') findings.push({ severity: 'error', code: 'secure-transport.envelope.integrity-self-invalid', message: 'Transport Envelope self-integrity does not verify.', reason: self.reason || self.state });
52
+ const crypto = qualifySecureTransportV1Envelope(parsed);
53
+ for (const item of crypto.findings || []) findings.push({ severity: item.kind === 'unsupported' ? 'error' : 'error', code: item.code, message: item.message });
54
+ if (!parsed.workspaceArtifactPath || !parsed.protectedPayloadDescriptorPath) findings.push({ severity: 'error', code: 'secure-transport.envelope.binding-links-invalid', message: 'Transport Envelope Workspace Artifact and Protected Payload links are required.' });
55
+ return deepFreeze({ status: findings.length ? (crypto.state === 'unsupported' ? 'unsupported' : 'invalid') : 'qualified', parsed, findings });
56
+ }
57
+
58
+ function parseSlots(text = '') {
59
+ const out = [];
60
+ const re = /^-\s+([^\n]+)\n((?:\s{2}-\s+[^\n]+\n?)*)/gm;
61
+ for (const match of String(text || '').matchAll(re)) {
62
+ const section = String(match[2] || '');
63
+ out.push(deepFreeze({
64
+ slotId: field(section, 'Slot Id'), slotKind: field(section, 'Slot Kind'), kdfAlgorithm: field(section, 'KDF Algorithm'), kdfSaltEncoding: field(section, 'KDF Salt Encoding'), kdfSalt: field(section, 'KDF Salt'), kdfParameters: jsonObjectField(section, 'KDF Parameters'), keyWrapAlgorithm: field(section, 'Key Wrap Algorithm'), keyWrapParameters: jsonObjectField(section, 'Key Wrap Parameters'), wrappedContentKeyEncoding: field(section, 'Wrapped Content Key Encoding'), wrappedContentKey: field(section, 'Wrapped Content Key'), slotVerificationRule: field(section, 'Slot Verification Rule'), ...(field(section, 'Recipient Hint') ? { recipientHint: field(section, 'Recipient Hint') } : {})
65
+ }));
66
+ }
67
+ return Object.freeze(out);
68
+ }
69
+ function jsonObjectField(section, name) { const raw = unquote(field(section, name)); try { const value = JSON.parse(raw); return value && typeof value === 'object' && !Array.isArray(value) ? value : null; } catch { return null; } }
70
+ function numberField(section, name) { const raw = field(section, name); return /^\d+$/.test(raw) ? Number(raw) : null; }
71
+ function renderParentEnvelope(parent = null) { if (!parent) return ''; const path=String(parent.path||'').trim(), label=String(parent.label||path||'Parent'), schemaId=String(parent.schemaId||'').trim(), schemaTarget=String(parent.schemaTarget||'').trim(), createdAt=normalizeCreatedAt(parent.createdAt||''); if(!path||!schemaId||!schemaTarget||!String(parent.selfDigest||'').trim()) throw new Error('secure-transport.parent-authority-incomplete'); return `- Parent\n - Parent Schema: [${schemaId}](${schemaTarget})\n - Created At: ${createdAt}\n - Trace: [${label}](${path})\n - Origin:\n - [relative](${path})\n`; }
72
+ function renderParentIntegrity(parent = null) { if(!parent) return ''; const path=String(parent.path||'').trim(), label=String(parent.label||path||'Parent'), digest=String(parent.selfDigest||'').trim(); if(!path||!digest) throw new Error('secure-transport.parent-integrity-incomplete'); return `- [sha256-base64url-c14n-v2](${C14N_V2_VALIDATOR_TARGET})\n - Towards: [${label}](${path})\n - Value: ${digest}\n\n`; }
73
+ function normalizeCreatedAt(value=''){const text=String(value||'').trim(); return text ? text.replace('T',' ').replace(/\.\d{3}Z$/,'').replace(/Z$/,'').slice(0,19) : '1970-01-01 00:00:00';}
74
+ function stableJson(value) { return JSON.stringify(sortJson(value)); }
75
+ function sortJson(value) { if (Array.isArray(value)) return value.map(sortJson); if (!value || typeof value !== 'object') return value; return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortJson(value[key])])); }
76
+ 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);}
@@ -50,3 +50,7 @@ export * from './handoff/manufacture.js';
50
50
  export * from './handoff/carrierProfile.js';
51
51
  export * from './handoff/toolingBootstrap.js';
52
52
  export * from './handoff/coldStartQualification.js';
53
+ export * from './handoff/transportEnvelopeV1.js';
54
+ export * from './handoff/recipientV2.packageV1.js';
55
+
56
+ export * from './comparison/sourceFrontierComparison.js';
@@ -38,10 +38,18 @@ import { describePortableColdStartIngress, groundPortableColdConsumer, projectPo
38
38
  import { projectPortableOperatingOverview } from './overview/operatingOverview.js';
39
39
  import { projectPortableGroundingReadiness } from './grounding/grounding.readiness.js';
40
40
  import { createPortablePackageOperationEntries } from './operation.catalog.package.js';
41
+ import { compareOrReconcilePortableSourceFrontiers } from './comparison/sourceFrontierComparison.js';
41
42
 
42
43
  export const PORTABLE_OPERATION_CATALOG_SCHEMA_ID = 'tiinex.portable.operation.catalog.v1';
43
44
 
44
45
  export const portableOperationCatalog = Object.freeze({
46
+ 'compare-source-frontiers': operation({
47
+ name: 'compare-source-frontiers',
48
+ description: 'Compare normalized exact Workspace source frontiers two-way or reconcile base/incoming/current three-way without merge, semantic inference, remote acquisition, or source mutation.',
49
+ safety: 'read-only',
50
+ inputSchema: 'tiinex.portable.source-frontier-comparison.request.v1',
51
+ handler: (input = {}) => wrapPortableResult('compare-source-frontiers', compareOrReconcilePortableSourceFrontiers(input))
52
+ }),
45
53
  'prepare-task': operation({
46
54
  name: 'prepare-task',
47
55
  description: 'Orchestrate host discovery, schema/provider resolution, schema guides, artifact planning, draft validation, lineage search, or asset analysis into one explicit next-action response.',
@@ -11,7 +11,6 @@ import { projectPortableEditorAssistance } from './editor/editor.assistance.js';
11
11
  import { projectQualifiedHandoffLeaves } from './handoff/handoffLeafProjection.js';
12
12
  import { projectPortableAuthoringParent } from './editor/authoring.parent.js';
13
13
  import { projectQualifiedWorkspacePackageSources } from './handoff/workspacePackageSources.js';
14
- import { compareSourceFrontiers } from './handoff/sourceFrontierComparison.js';
15
14
  import { projectPortableHandoffAuthoringPlan } from './handoff/handoffAuthoringPlan.js';
16
15
  import { projectQualifiedHandoffEndpoints } from './handoff/handoffEndpointProjection.js';
17
16
  import { projectPortableOperatorContext } from './handoff/operatorContextProjection.js';
@@ -96,13 +95,6 @@ export function createPortablePackageOperationEntries({ operation, wrapPortableR
96
95
  inputSchema: 'tiinex.portable.input.v1',
97
96
  handler: (input = {}) => wrapPortableResult('project-workspace-package-sources', projectQualifiedWorkspacePackageSources(input))
98
97
  }),
99
- 'compare-source-frontiers': operation({
100
- name: 'compare-source-frontiers',
101
- description: 'Compare one local Workspace root against one carried workspace snapshot bound by a recipient-facing Handoff package.',
102
- safety: 'read-only',
103
- inputSchema: 'tiinex.portable.input.v1',
104
- handler: async (input = {}) => wrapPortableResult('compare-source-frontiers', await compareSourceFrontiers(input))
105
- }),
106
98
  'project-handoff-authoring-plan': operation({
107
99
  name: 'project-handoff-authoring-plan',
108
100
  description: 'Project the shared root or continuation path allocation for native Handoff authoring from exact local material without creating an artifact.',