@tiinex/core 0.11.0 → 0.13.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 +6 -5
- package/src/audit/audit.run.js +1 -1
- package/src/public/index.js +18 -0
- package/src/public/node.js +1 -0
- package/src/schemas/creation.contracts.js +1 -1
- package/src/schemas/creation.schemaReferences.js +28 -8
- package/src/schemas/schema.reference.js +30 -4
- package/src/schemas/tiinex.root.v1.schema.json +2 -1
- package/src/tooling/portable/adapters/cli/cli.command-input.js +5 -0
- package/src/tooling/portable/adapters/cli/cli.common-output.js +23 -0
- package/src/tooling/portable/adapters/cli/cli.handoff-manufacture.js +5 -1
- package/src/tooling/portable/adapters/cli/cli.help.js +15 -2
- package/src/tooling/portable/adapters/cli/cli.run.js +1 -1
- package/src/tooling/portable/adapters/cli/cli.source-frontier-comparison.js +6 -6
- package/src/tooling/portable/adapters/cli/cli.source-frontier-reconciliation.js +18 -0
- package/src/tooling/portable/adapters/node/handoff.manufacture.bootstrap.js +37 -1
- package/src/tooling/portable/adapters/node/handoff.manufacture.enumeration.js +27 -8
- package/src/tooling/portable/adapters/node/handoff.manufacture.js +27 -0
- package/src/tooling/portable/adapters/node/handoff.manufacture.runtimeSource.js +82 -0
- package/src/tooling/portable/adapters/node/sourceFrontierComparison.js +2 -1
- package/src/tooling/portable/adapters/node/sourceFrontierReconciliationProof.js +23 -0
- package/src/tooling/portable/adapters/node/workspaceCarrier.manufacture.js +8 -1
- package/src/tooling/portable/audit/audit.capability.js +2 -1
- package/src/tooling/portable/comparison/sourceFrontierComparison.js +54 -4
- package/src/tooling/portable/comparison/sourceFrontierReconciliationProof.js +569 -0
- package/src/tooling/portable/draft/draft.create.js +3 -0
- package/src/tooling/portable/draft/draft.operations.js +1 -1
- package/src/tooling/portable/editor/editor.assistance.js +2 -25
- package/src/tooling/portable/handoff/manufacture.js +15 -3
- package/src/tooling/portable/handoff/pointerEntrypoint.js +4 -1
- package/src/tooling/portable/handoff/schemaReferencePreflight.js +26 -0
- package/src/tooling/portable/handoff/transportEnvelopeV1.js +5 -0
- package/src/tooling/portable/index.js +1 -0
- package/src/tooling/portable/operation.catalog.js +8 -0
- package/src/tooling/portable/source/sourceEligibility.js +108 -0
- package/src/validation/validateArtifact.js +62 -6
|
@@ -2,10 +2,13 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { inferWorkspaceTitle, normalizeAdditionalWorkspaceDescriptors, normalizeTransportRoute, safeWorkspaceToken } from './handoff.manufacture.multiRoot.js';
|
|
4
4
|
import { buildToolingBootstrapTransportFiles, PORTABLE_TOOLING_BOOTSTRAP_MANIFEST_SCHEMA_ID } from './handoff.manufacture.bootstrap.js';
|
|
5
|
+
import { qualifyToolingRuntimeSourceAlignment } from './handoff.manufacture.runtimeSource.js';
|
|
5
6
|
import { normalizeHandoffCarrierLineage } from '../../handoff/carrierLineage.js';
|
|
6
7
|
import { normalizeHandoffCarrierProfile } from '../../handoff/carrierProfile.js';
|
|
7
8
|
import { enumerateNodeWorkspace, PORTABLE_NODE_WORKSPACE_ENUMERATION_SCHEMA_ID } from './handoff.manufacture.enumeration.js';
|
|
8
9
|
import { preparePackageParentWorkspaceReuse } from './handoff.manufacture.packageParent.js';
|
|
10
|
+
import { qualifyPortableSourceReconciliationProofForManufacture } from '../../comparison/sourceFrontierReconciliationProof.js';
|
|
11
|
+
import { qualifyPortableManufactureSchemaReferenceCandidate } from '../../handoff/schemaReferencePreflight.js';
|
|
9
12
|
import {
|
|
10
13
|
assertInside,
|
|
11
14
|
expandPointerDependencyClosure,
|
|
@@ -47,6 +50,7 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
|
|
|
47
50
|
workspaceTitle: requestedWorkspaceTitle,
|
|
48
51
|
sourceMetadata: input.workspaceSource || input.sourceMetadata || {},
|
|
49
52
|
excludeDirectories: input.excludeDirectories || options.excludeDirectories,
|
|
53
|
+
excludeRelativePaths: input.excludeRelativePaths || options.excludeRelativePaths,
|
|
50
54
|
maxFiles: input.maxFiles || options.maxFiles
|
|
51
55
|
});
|
|
52
56
|
const additionalWorkspaceDescriptors = normalizeAdditionalWorkspaceDescriptors(input.additionalWorkspaces || input.workspaceRoots || input.workspaceDescriptors || []);
|
|
@@ -78,6 +82,7 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
|
|
|
78
82
|
workspaceTitle: requestedTitle,
|
|
79
83
|
sourceMetadata: descriptor.source || descriptor.sourceMetadata || {},
|
|
80
84
|
excludeDirectories: descriptor.excludeDirectories || input.excludeDirectories || options.excludeDirectories,
|
|
85
|
+
excludeRelativePaths: descriptor.excludeRelativePaths || input.excludeRelativePaths || options.excludeRelativePaths,
|
|
81
86
|
maxFiles: descriptor.maxFiles || input.maxFiles || options.maxFiles
|
|
82
87
|
});
|
|
83
88
|
if (enumerated.status !== 'qualified-complete') throw new Error(`portable.handoff-manufacture.workspace-enumeration.${id}.${enumerated.status}`);
|
|
@@ -96,6 +101,8 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
|
|
|
96
101
|
markdown: handoffMarkdown
|
|
97
102
|
});
|
|
98
103
|
|
|
104
|
+
const schemaReferencePreflight = qualifyPortableManufactureSchemaReferenceCandidate(handoff);
|
|
105
|
+
|
|
99
106
|
if (enumeration.status !== 'qualified-complete') throw new Error(`portable.handoff-manufacture.workspace-enumeration.${enumeration.status}`);
|
|
100
107
|
const workspaceTitle = requestedWorkspaceTitle || inferWorkspaceTitle(enumeration) || workspaceId;
|
|
101
108
|
const primaryMaterialization = Object.freeze({ ...enumeration.materialization, title: workspaceTitle });
|
|
@@ -137,6 +144,13 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
|
|
|
137
144
|
if (targets.length !== 1) throw new Error(`portable.handoff-manufacture.workspace-scope.target-${targets.length ? 'ambiguous' : 'required'}:${materialization.id}`);
|
|
138
145
|
workspaceMaterializations[index] = projectBoundedWorkspaceMaterialization(materialization, scope, targets[0].path);
|
|
139
146
|
}
|
|
147
|
+
const hasReconciliationProof = Boolean(input.reconciliationProof && typeof input.reconciliationProof === 'object' && Object.keys(input.reconciliationProof).length);
|
|
148
|
+
const reconciliationProofQualification = qualifyPortableSourceReconciliationProofForManufacture({
|
|
149
|
+
proof: input.reconciliationProof || null,
|
|
150
|
+
requireProof: input.requireReconciliationProof === true,
|
|
151
|
+
workspaceMaterializations,
|
|
152
|
+
requiredWorkspaceIds: hasReconciliationProof || input.requireReconciliationProof === true ? [workspaceId] : []
|
|
153
|
+
});
|
|
140
154
|
|
|
141
155
|
const routeSpecs = transportRoutes.length ? transportRoutes : Object.freeze([{ workspaceId, path: handoffPath }]);
|
|
142
156
|
let requirements = await projectManufacturingRequirements({ handoff, workspaceId, handoffPath, routeSpecs, workspaceRuntimeById });
|
|
@@ -153,6 +167,14 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
|
|
|
153
167
|
const toolingBootstrapResult = await toolingBootstrapPromise;
|
|
154
168
|
if (toolingBootstrapResult.error) throw toolingBootstrapResult.error;
|
|
155
169
|
const toolingBootstrap = toolingBootstrapResult.value;
|
|
170
|
+
const runtimeSourceAlignment = await qualifyToolingRuntimeSourceAlignment({
|
|
171
|
+
runtimeIdentity: toolingBootstrap.runtimeIdentity,
|
|
172
|
+
localWorkspaces: [
|
|
173
|
+
Object.freeze({ id: workspaceId, root: workspaceRoot, materialization: primaryMaterialization }),
|
|
174
|
+
...additionalEnumerations.map(({ id, root, enumerated }) => Object.freeze({ id, root, materialization: enumerated.materialization }))
|
|
175
|
+
],
|
|
176
|
+
maxFiles: input.bootstrapMaxFiles || options.bootstrapMaxFiles
|
|
177
|
+
});
|
|
156
178
|
const orientationBootstrap = input.transportBootstrapContent
|
|
157
179
|
? Object.freeze({ present: true, path: String(input.transportBootstrapPath || 'tiinex.package/bootstrap.md'), content: String(input.transportBootstrapContent), mediaType: 'text/markdown' })
|
|
158
180
|
: Object.freeze({ present: false });
|
|
@@ -171,10 +193,15 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
|
|
|
171
193
|
carrierLineage: normalizeHandoffCarrierLineage(input.carrierLineage || null),
|
|
172
194
|
carrierProfile: normalizeHandoffCarrierProfile(input.carrierProfile || null),
|
|
173
195
|
toolingBootstrap: toolingBootstrap.summary,
|
|
196
|
+
reconciliationProofQualification,
|
|
197
|
+
schemaReferencePreflight,
|
|
174
198
|
manufacturingEvidence: Object.freeze({
|
|
175
199
|
enumeration: enumeration.evidence,
|
|
176
200
|
workspaceEnumerations: Object.freeze(workspaceEnumerations),
|
|
177
201
|
toolingBootstrap: toolingBootstrap.summary,
|
|
202
|
+
runtimeSourceAlignment,
|
|
203
|
+
reconciliationProof: reconciliationProofQualification,
|
|
204
|
+
schemaReferencePreflight,
|
|
178
205
|
packageParentWorkspaceReuse: Object.freeze({
|
|
179
206
|
state: String(packageParentReuse.state || ''),
|
|
180
207
|
providerState: String(packageParentReuse.providerState || ''),
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { buildToolingBootstrapRuntimeIdentity } from './handoff.manufacture.bootstrap.js';
|
|
2
|
+
|
|
3
|
+
export const PORTABLE_TOOLING_RUNTIME_SOURCE_ALIGNMENT_SCHEMA_ID = 'tiinex.portable.tooling-runtime-source-alignment.v1';
|
|
4
|
+
const CORE_PACKAGE_NAME = '@tiinex/core';
|
|
5
|
+
|
|
6
|
+
export async function qualifyToolingRuntimeSourceAlignment(input = {}) {
|
|
7
|
+
const runtimeIdentity = normalizeIdentity(input.runtimeIdentity);
|
|
8
|
+
if (!runtimeIdentity.sourceRepresentationSha256) throw new Error('portable.tooling-bootstrap.runtime-source.runtime-identity-required');
|
|
9
|
+
const candidates = [];
|
|
10
|
+
for (const item of input.localWorkspaces || input.workspaces || []) {
|
|
11
|
+
const root = String(item?.root || '').trim();
|
|
12
|
+
if (!root) continue;
|
|
13
|
+
const materialization = item?.materialization || item?.enumeration?.materialization || null;
|
|
14
|
+
const packageIdentity = packageIdentityFromMaterialization(materialization);
|
|
15
|
+
if (packageIdentity.name !== CORE_PACKAGE_NAME) continue;
|
|
16
|
+
candidates.push(Object.freeze({
|
|
17
|
+
workspaceId: String(item?.id || materialization?.id || '').trim(),
|
|
18
|
+
root,
|
|
19
|
+
packageIdentity
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
if (!candidates.length) return Object.freeze({
|
|
23
|
+
schema: PORTABLE_TOOLING_RUNTIME_SOURCE_ALIGNMENT_SCHEMA_ID,
|
|
24
|
+
state: 'not-observed',
|
|
25
|
+
coreWorkspaceCount: 0,
|
|
26
|
+
runtime: runtimeIdentity,
|
|
27
|
+
source: null,
|
|
28
|
+
boundary: 'No local Workspace with package identity @tiinex/core was selected, so exact runtime/source equality cannot be asserted from carried source.'
|
|
29
|
+
});
|
|
30
|
+
if (candidates.length !== 1) throw new Error(`portable.tooling-bootstrap.runtime-source.core-workspace-ambiguous:${candidates.map((item) => item.workspaceId || 'unlabeled').join(',')}`);
|
|
31
|
+
|
|
32
|
+
const candidate = candidates[0];
|
|
33
|
+
const sourceIdentity = normalizeIdentity(await buildToolingBootstrapRuntimeIdentity({
|
|
34
|
+
runtimeRoot: candidate.root,
|
|
35
|
+
maxFiles: input.maxFiles
|
|
36
|
+
}));
|
|
37
|
+
const evidence = Object.freeze({
|
|
38
|
+
schema: PORTABLE_TOOLING_RUNTIME_SOURCE_ALIGNMENT_SCHEMA_ID,
|
|
39
|
+
state: sourceIdentity.sourceRepresentationSha256 === runtimeIdentity.sourceRepresentationSha256 ? 'qualified-exact-match' : 'mismatch',
|
|
40
|
+
coreWorkspaceCount: 1,
|
|
41
|
+
workspaceId: candidate.workspaceId,
|
|
42
|
+
runtime: runtimeIdentity,
|
|
43
|
+
source: sourceIdentity,
|
|
44
|
+
boundary: 'Exact Tooling runtime source/data equality is required when a local @tiinex/core Workspace is carried. Release-normalized package.json metadata may differ, but matching package names or versions alone are insufficient.'
|
|
45
|
+
});
|
|
46
|
+
if (evidence.state !== 'qualified-exact-match') {
|
|
47
|
+
throw new Error([
|
|
48
|
+
'portable.tooling-bootstrap.runtime-source.mismatch',
|
|
49
|
+
`workspace=${candidate.workspaceId || 'unlabeled'}`,
|
|
50
|
+
`runtimeVersion=${runtimeIdentity.packageVersion || 'unknown'}`,
|
|
51
|
+
`sourceVersion=${sourceIdentity.packageVersion || candidate.packageIdentity.version || 'unknown'}`,
|
|
52
|
+
`runtimeSource=${runtimeIdentity.sourceRepresentationSha256}`,
|
|
53
|
+
`sourceSource=${sourceIdentity.sourceRepresentationSha256}`
|
|
54
|
+
].join(':'));
|
|
55
|
+
}
|
|
56
|
+
return evidence;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function packageIdentityFromMaterialization(materialization) {
|
|
60
|
+
const entry = (materialization?.entries || []).find((item) => String(item?.path || '') === 'package.json');
|
|
61
|
+
if (!entry?.data) return Object.freeze({ name: '', version: '' });
|
|
62
|
+
try {
|
|
63
|
+
const parsed = JSON.parse(new TextDecoder().decode(entry.data));
|
|
64
|
+
return Object.freeze({ name: String(parsed?.name || '').trim(), version: String(parsed?.version || '').trim() });
|
|
65
|
+
} catch {
|
|
66
|
+
return Object.freeze({ name: '', version: '' });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function normalizeIdentity(value = {}) {
|
|
71
|
+
return Object.freeze({
|
|
72
|
+
schema: String(value?.schema || 'tiinex.portable.tooling-runtime-identity.v1'),
|
|
73
|
+
packageName: String(value?.packageName || ''),
|
|
74
|
+
packageVersion: String(value?.packageVersion || ''),
|
|
75
|
+
representationSha256: String(value?.representationSha256 || ''),
|
|
76
|
+
sourceRepresentationSha256: String(value?.sourceRepresentationSha256 || ''),
|
|
77
|
+
runtimeFiles: Number(value?.runtimeFiles || 0),
|
|
78
|
+
runtimeBytes: Number(value?.runtimeBytes || 0),
|
|
79
|
+
entrypointSha256: String(value?.entrypointSha256 || ''),
|
|
80
|
+
enumerationPolicySha256: String(value?.enumerationPolicySha256 || '')
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -59,7 +59,7 @@ async function frontierFromLocalWorkspace(descriptor, options) {
|
|
|
59
59
|
id: descriptor.label || `${options.side || 'input'}:${workspaceId}`,
|
|
60
60
|
source: { kind: 'local-workspace', ref: root, snapshotContract: 'shared-handoff-manufacture-node-enumeration-v1' },
|
|
61
61
|
workspaces: [workspaceFromEnumeration(workspaceId, enumeration, root)],
|
|
62
|
-
boundary: 'Local source is enumerated by the same deterministic Node Workspace enumerator used by Handoff manufacture;
|
|
62
|
+
boundary: 'Local source is enumerated by the same deterministic Node Workspace enumerator used by Handoff manufacture; durable-source eligibility, generated-cache exclusions, configured path exclusions, and symlink policy therefore remain shared.'
|
|
63
63
|
});
|
|
64
64
|
} catch (error) {
|
|
65
65
|
return createPortableSourceFrontier({ id: descriptor.label || options.side || '', state: 'qualification-error', source: { kind: 'local-workspace', ref: root }, workspaces: [{ workspaceId, state: 'unavailable', reason: 'local-workspace-read-failed', source: { kind: 'local-workspace', ref: root } }], findings: [portableFinding('error', 'portable.source-frontier.node.local-read-failed', 'Local Workspace source could not be enumerated.', { workspaceId, ref: root, detail: String(error?.message || error || '') })] });
|
|
@@ -180,6 +180,7 @@ function enumerationOptions(descriptor, options, workspaceId) {
|
|
|
180
180
|
workspaceTitle: String(descriptor.workspaceTitle || descriptor.title || workspaceId),
|
|
181
181
|
sourceMetadata: descriptor.sourceMetadata || {},
|
|
182
182
|
excludeDirectories: descriptor.excludeDirectories || options.excludeDirectories,
|
|
183
|
+
excludeRelativePaths: descriptor.excludeRelativePaths || options.excludeRelativePaths,
|
|
183
184
|
maxFiles: descriptor.maxFiles || options.maxFiles
|
|
184
185
|
};
|
|
185
186
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { prepareNodeSourceFrontier } from './sourceFrontierComparison.js';
|
|
2
|
+
import { provePortableSourceReconciliation } from '../../comparison/sourceFrontierReconciliationProof.js';
|
|
3
|
+
|
|
4
|
+
export async function prepareNodeSourceReconciliationProofInput(input = {}, options = {}) {
|
|
5
|
+
const [base, incoming, current, reconciled] = await Promise.all([
|
|
6
|
+
prepareNodeSourceFrontier(input.base, { ...options, side: 'base' }),
|
|
7
|
+
prepareNodeSourceFrontier(input.incoming, { ...options, side: 'incoming' }),
|
|
8
|
+
prepareNodeSourceFrontier(input.current, { ...options, side: 'current' }),
|
|
9
|
+
prepareNodeSourceFrontier(input.reconciled, { ...options, side: 'reconciled' })
|
|
10
|
+
]);
|
|
11
|
+
return Object.freeze({
|
|
12
|
+
base,
|
|
13
|
+
incoming,
|
|
14
|
+
current,
|
|
15
|
+
reconciled,
|
|
16
|
+
dispositions: input.dispositions || []
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function proveNodeSourceReconciliation(input = {}, options = {}) {
|
|
21
|
+
const normalized = await prepareNodeSourceReconciliationProofInput(input, options);
|
|
22
|
+
return provePortableSourceReconciliation(normalized);
|
|
23
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { enumerateNodeWorkspace } from './handoff.manufacture.enumeration.js';
|
|
3
3
|
import { buildToolingBootstrapTransportFiles } from './handoff.manufacture.bootstrap.js';
|
|
4
|
+
import { qualifyToolingRuntimeSourceAlignment } from './handoff.manufacture.runtimeSource.js';
|
|
4
5
|
import { inferWorkspaceTitle, normalizeAdditionalWorkspaceDescriptors, safeWorkspaceToken } from './handoff.manufacture.multiRoot.js';
|
|
5
6
|
import { normalizeWorkspaceTargetBindings } from './handoff.manufacture.scope.js';
|
|
6
7
|
import { normalizeHandoffCarrierLineage } from '../../handoff/carrierLineage.js';
|
|
@@ -28,6 +29,7 @@ export async function prepareNodeWorkspaceCarrierManufacturingInput(input = {},
|
|
|
28
29
|
workspaceTitle: descriptor.title,
|
|
29
30
|
sourceMetadata: descriptor.source || descriptor.sourceMetadata || {},
|
|
30
31
|
excludeDirectories: descriptor.excludeDirectories || input.excludeDirectories || options.excludeDirectories,
|
|
32
|
+
excludeRelativePaths: descriptor.excludeRelativePaths || input.excludeRelativePaths || options.excludeRelativePaths,
|
|
31
33
|
maxFiles: descriptor.maxFiles || input.maxFiles || options.maxFiles
|
|
32
34
|
});
|
|
33
35
|
if (enumeration.status !== 'qualified-complete') throw new Error(`portable.workspace-carrier.workspace-enumeration.${descriptor.id}.${enumeration.status}`);
|
|
@@ -47,6 +49,11 @@ export async function prepareNodeWorkspaceCarrierManufacturingInput(input = {},
|
|
|
47
49
|
expected: input.expectedToolingBootstrap || null,
|
|
48
50
|
maxFiles: input.bootstrapMaxFiles || options.bootstrapMaxFiles
|
|
49
51
|
});
|
|
52
|
+
const runtimeSourceAlignment = await qualifyToolingRuntimeSourceAlignment({
|
|
53
|
+
runtimeIdentity: toolingBootstrap.runtimeIdentity,
|
|
54
|
+
localWorkspaces: enumerations.map((item) => Object.freeze({ id: item.materialization.id, root: item.root, materialization: item.materialization })),
|
|
55
|
+
maxFiles: input.bootstrapMaxFiles || options.bootstrapMaxFiles
|
|
56
|
+
});
|
|
50
57
|
return Object.freeze({
|
|
51
58
|
carrierMode: 'workspace',
|
|
52
59
|
createdAt: String(input.createdAt || ''),
|
|
@@ -56,7 +63,7 @@ export async function prepareNodeWorkspaceCarrierManufacturingInput(input = {},
|
|
|
56
63
|
carrierLineage: normalizeHandoffCarrierLineage(input.carrierLineage || null),
|
|
57
64
|
carrierProfile: normalizeHandoffCarrierProfile(input.carrierProfile || null),
|
|
58
65
|
toolingBootstrap: toolingBootstrap.summary,
|
|
59
|
-
manufacturingEvidence: Object.freeze({ workspaceEnumerations: Object.freeze(enumerations.map((item) => Object.freeze({ id: item.materialization.id, root: item.root, evidence: item.evidence }))), toolingBootstrap: toolingBootstrap.summary }),
|
|
66
|
+
manufacturingEvidence: Object.freeze({ workspaceEnumerations: Object.freeze(enumerations.map((item) => Object.freeze({ id: item.materialization.id, root: item.root, evidence: item.evidence }))), toolingBootstrap: toolingBootstrap.summary, runtimeSourceAlignment }),
|
|
60
67
|
verifyRoundtrip: input.verifyRoundtrip !== false
|
|
61
68
|
});
|
|
62
69
|
}
|
|
@@ -16,7 +16,8 @@ export function auditPortableRecord(record = {}, options = {}) {
|
|
|
16
16
|
record,
|
|
17
17
|
markdown: record.markdown,
|
|
18
18
|
validationContractOverride: runtimeProjection?.state === 'qualified' ? runtimeProjection.compiledContract : null,
|
|
19
|
-
schemaValidationAuthority: requireExactSchemaAuthority ? runtimeAuthority : null
|
|
19
|
+
schemaValidationAuthority: requireExactSchemaAuthority ? runtimeAuthority : null,
|
|
20
|
+
schemaReferenceContext: options.schemaReferenceContext || 'historical'
|
|
20
21
|
});
|
|
21
22
|
} catch (error) {
|
|
22
23
|
const finding = portableFinding('error', 'portable.audit.exception', error?.message || 'Audit failed.', {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { sha256Hex, utf8Bytes } from '../../../export/package.bytes.js';
|
|
2
2
|
import { portableFinding, summarizePortableFindings } from '../findings.js';
|
|
3
|
+
import { portableSourceEligibilityPolicy, qualifyPortableSourcePath } from '../source/sourceEligibility.js';
|
|
3
4
|
|
|
4
5
|
export const PORTABLE_SOURCE_FRONTIER_SCHEMA_ID = 'tiinex.portable.source-frontier.v1';
|
|
5
6
|
export const PORTABLE_SOURCE_FRONTIER_COMPARISON_SCHEMA_ID = 'tiinex.portable.source-frontier-comparison.v1';
|
|
@@ -45,16 +46,34 @@ export function createPortableSourceFrontier(input = {}) {
|
|
|
45
46
|
workspaces: Object.freeze(workspaces),
|
|
46
47
|
findings: normalizedFindings,
|
|
47
48
|
findingSummary: summarizePortableFindings(normalizedFindings),
|
|
48
|
-
boundary: String(input.boundary || '
|
|
49
|
+
boundary: String(input.boundary || 'Eligible durable source-byte frontier only. Source-ineligible transport/runtime/cache bytes are outside frontier identity; Workspace/path equality does not establish semantic equivalence, authority, acceptance, deletion, or merge disposition.')
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
export function createPortableWorkspaceSnapshot(entries = [], evidence = {}) {
|
|
53
54
|
const findings = [];
|
|
54
|
-
const
|
|
55
|
+
const selection = normalizeEntries(entries, findings);
|
|
55
56
|
if (findings.length) return deepFreeze({ state: 'qualification-error', entries: Object.freeze([]), findings: Object.freeze(findings) });
|
|
57
|
+
const normalized = selection.entries;
|
|
56
58
|
const totalBytes = normalized.reduce((sum, entry) => sum + entry.bytes, 0);
|
|
57
59
|
const fingerprint = snapshotFingerprint(normalized);
|
|
60
|
+
const inheritedEvidence = serializableObject(evidence);
|
|
61
|
+
const priorEligibility = inheritedEvidence?.sourceEligibility && typeof inheritedEvidence.sourceEligibility === 'object'
|
|
62
|
+
? inheritedEvidence.sourceEligibility
|
|
63
|
+
: null;
|
|
64
|
+
const upstreamSelection = priorEligibility?.upstreamSelection || (priorEligibility && Number(priorEligibility.excludedEntryCount || 0) > 0 ? portableSourceSelectionReceipt(priorEligibility) : null);
|
|
65
|
+
const sourceEligibility = Object.freeze({
|
|
66
|
+
schema: selection.policy.schema,
|
|
67
|
+
inputEntryCount: selection.inputEntryCount,
|
|
68
|
+
eligibleEntryCount: normalized.length,
|
|
69
|
+
excludedEntryCount: selection.excludedEntryCount,
|
|
70
|
+
excludedByReason: Object.freeze({ ...selection.excludedByReason }),
|
|
71
|
+
excludedDirectories: selection.policy.excludedDirectories,
|
|
72
|
+
excludedRelativePaths: selection.policy.excludedRelativePaths,
|
|
73
|
+
excludedFileSuffixes: selection.policy.excludedFileSuffixes,
|
|
74
|
+
...(upstreamSelection ? { upstreamSelection: Object.freeze(upstreamSelection) } : {}),
|
|
75
|
+
boundary: selection.policy.boundary
|
|
76
|
+
});
|
|
58
77
|
return deepFreeze({
|
|
59
78
|
state: QUALIFIED,
|
|
60
79
|
entryCount: normalized.length,
|
|
@@ -62,7 +81,7 @@ export function createPortableWorkspaceSnapshot(entries = [], evidence = {}) {
|
|
|
62
81
|
fingerprint,
|
|
63
82
|
fingerprintMethod: 'sha256-path-bytes-sha256-v1',
|
|
64
83
|
entries: Object.freeze(normalized),
|
|
65
|
-
evidence: Object.freeze(
|
|
84
|
+
evidence: Object.freeze({ ...inheritedEvidence, sourceEligibility }),
|
|
66
85
|
findings: Object.freeze([])
|
|
67
86
|
});
|
|
68
87
|
}
|
|
@@ -181,13 +200,28 @@ function normalizeWorkspace(raw, workspaceId, findings) {
|
|
|
181
200
|
function normalizeEntries(entries, findings) {
|
|
182
201
|
const out = [];
|
|
183
202
|
const seen = new Set();
|
|
203
|
+
const policy = portableSourceEligibilityPolicy();
|
|
204
|
+
const excludedByReason = {};
|
|
205
|
+
let inputEntryCount = 0;
|
|
206
|
+
let excludedEntryCount = 0;
|
|
184
207
|
for (const raw of entries || []) {
|
|
208
|
+
inputEntryCount += 1;
|
|
185
209
|
const rawPath = String(raw?.path || raw?.innerPath || '').trim();
|
|
186
210
|
const path = normalizePath(rawPath);
|
|
187
211
|
if (unsafeRawPath(rawPath) || !path || unsafePath(path)) {
|
|
188
212
|
findings.push(portableFinding('error', 'portable.source-frontier.entry.path-invalid', 'Comparable source entry has an unsafe or empty Workspace-relative path.', { path: rawPath }));
|
|
189
213
|
continue;
|
|
190
214
|
}
|
|
215
|
+
const eligibility = qualifyPortableSourcePath(path, {
|
|
216
|
+
excludeDirectories: policy.excludedDirectories,
|
|
217
|
+
excludeRelativePaths: policy.excludedRelativePaths,
|
|
218
|
+
entryKind: 'file'
|
|
219
|
+
});
|
|
220
|
+
if (!eligibility.eligible) {
|
|
221
|
+
excludedEntryCount += 1;
|
|
222
|
+
excludedByReason[eligibility.reason] = Number(excludedByReason[eligibility.reason] || 0) + 1;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
191
225
|
if (seen.has(path)) {
|
|
192
226
|
findings.push(portableFinding('error', 'portable.source-frontier.entry.path-duplicate', 'Comparable source snapshot contains a duplicate normalized path.', { path }));
|
|
193
227
|
continue;
|
|
@@ -202,7 +236,23 @@ function normalizeEntries(entries, findings) {
|
|
|
202
236
|
out.push(Object.freeze({ path, bytes, sha256 }));
|
|
203
237
|
}
|
|
204
238
|
out.sort((a, b) => a.path.localeCompare(b.path));
|
|
205
|
-
return
|
|
239
|
+
return Object.freeze({
|
|
240
|
+
entries: Object.freeze(out),
|
|
241
|
+
policy,
|
|
242
|
+
inputEntryCount,
|
|
243
|
+
excludedEntryCount,
|
|
244
|
+
excludedByReason: Object.freeze(Object.fromEntries(Object.entries(excludedByReason).sort(([a], [b]) => a.localeCompare(b))))
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function portableSourceSelectionReceipt(value = {}) {
|
|
249
|
+
return {
|
|
250
|
+
schema: String(value.schema || ''),
|
|
251
|
+
inputEntryCount: Number(value.inputEntryCount || 0),
|
|
252
|
+
eligibleEntryCount: Number(value.eligibleEntryCount || 0),
|
|
253
|
+
excludedEntryCount: Number(value.excludedEntryCount || 0),
|
|
254
|
+
excludedByReason: Object.freeze({ ...serializableObject(value.excludedByReason || {}) })
|
|
255
|
+
};
|
|
206
256
|
}
|
|
207
257
|
|
|
208
258
|
function compareWorkspaceUnion(left, right) {
|