@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.
- package/package.json +8 -6
- package/src/public/index.js +21 -0
- package/src/public/node.js +1 -0
- package/src/release/plan.mjs +18 -5
- package/src/release/run.mjs +7 -5
- package/src/tooling/portable/adapters/cli/cli.command-input.js +3 -0
- package/src/tooling/portable/adapters/cli/cli.common-output.js +23 -0
- package/src/tooling/portable/adapters/cli/cli.handoff-manufacture.js +3 -1
- package/src/tooling/portable/adapters/cli/cli.help.js +18 -2
- package/src/tooling/portable/adapters/cli/cli.material-policy.js +0 -1
- package/src/tooling/portable/adapters/cli/cli.operator-bridge.js +0 -13
- package/src/tooling/portable/adapters/cli/cli.run.js +1 -1
- package/src/tooling/portable/adapters/cli/cli.source-frontier-comparison.js +50 -0
- package/src/tooling/portable/adapters/node/handoff.manufacture.js +14 -0
- package/src/tooling/portable/adapters/node/handoff.manufacture.packageParent.js +80 -16
- package/src/tooling/portable/adapters/node/handoff.manufacture.requirements.js +44 -11
- package/src/tooling/portable/adapters/node/handoff.manufacture.scope.js +96 -1
- package/src/tooling/portable/adapters/node/sourceFrontierComparison.js +194 -0
- package/src/tooling/portable/comparison/sourceFrontierComparison.js +501 -0
- package/src/tooling/portable/grounding/grounding.readiness.js +8 -4
- package/src/tooling/portable/grounding/grounding.readiness.support.js +63 -1
- package/src/tooling/portable/handoff/contextAudit.js +21 -1
- package/src/tooling/portable/handoff/materialClosure.descriptor.js +1 -1
- package/src/tooling/portable/handoff/materialClosure.materials.js +1 -1
- package/src/tooling/portable/handoff/recipientV2.artifacts.js +1 -1
- package/src/tooling/portable/handoff/recipientV2.inspect.helpers.js +6 -1
- package/src/tooling/portable/handoff/recipientV2.packageV1.build.js +93 -19
- package/src/tooling/portable/handoff/recipientV2.packageV1.contract.js +30 -1
- package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.helpers.js +8 -2
- package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.js +122 -10
- package/src/tooling/portable/handoff/recipientV2.packageV1.js +2 -1
- package/src/tooling/portable/handoff/recipientV2.packageV1.secure.js +87 -0
- package/src/tooling/portable/handoff/recipientV2.packageV1.shared.js +9 -1
- package/src/tooling/portable/handoff/recipientV2.topology.js +2 -2
- package/src/tooling/portable/handoff/recipientV2.topology.materials.js +9 -1
- package/src/tooling/portable/handoff/transportEnvelopeV1.js +76 -0
- package/src/tooling/portable/index.js +4 -0
- package/src/tooling/portable/operation.catalog.js +8 -0
- package/src/tooling/portable/operation.catalog.package.js +0 -8
- package/src/transport/secureTransportV1.js +339 -0
- 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
|
-
|
|
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
|
|
33
|
-
// Route closure remains selected and minimal, but
|
|
34
|
-
//
|
|
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
|
-
|
|
60
|
-
files.push(workspaceFile, archiveFile);
|
|
69
|
+
files.push(workspaceFile);
|
|
61
70
|
const parent = recipientV2ParentAuthority(workspaceFile, 'tiinex.workspace.v1', workspaceSchemaTarget(targetQualification), createdAt);
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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,
|
|
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) =>
|
|
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;
|