@tiinex/core 0.13.0 → 0.15.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 +5 -5
- package/src/integrity/integrity.c14nV1.js +28 -0
- package/src/lineage/lineage.integrity.js +108 -22
- package/src/lineage/lineage.resolve.js +21 -14
- package/src/lineage/lineage.sourceScope.js +41 -12
- package/src/lineage/lineage.targetKeys.js +26 -7
- package/src/tooling/portable/adapters/cli/cli.handoff-manufacture.js +47 -10
- package/src/tooling/portable/adapters/node/bootstrapCarrier.manufacture.js +18 -0
- package/src/tooling/portable/adapters/node/workspaceCarrier.manufacture.js +13 -3
- package/src/tooling/portable/bootstrap/tiinex.llm.bootstrap.md +2 -2
- package/src/tooling/portable/handoff/bootstrapCarrier.manufacture.js +56 -0
- package/src/tooling/portable/handoff/contracts/tiinex.handoff.package.v1.schema.md +340 -77
- package/src/tooling/portable/handoff/manufacture.js +3 -1
- package/src/tooling/portable/handoff/recipientV2.artifactFirst.build.js +73 -32
- package/src/tooling/portable/handoff/recipientV2.artifactFirst.closure.js +1 -1
- package/src/tooling/portable/handoff/recipientV2.artifactFirst.inspect.js +9 -1
- package/src/tooling/portable/handoff/recipientV2.artifactFirst.materials.js +28 -6
- package/src/tooling/portable/handoff/recipientV2.artifactFirst.shared.js +8 -0
- package/src/tooling/portable/handoff/recipientV2.coldProjection.js +11 -6
- package/src/tooling/portable/handoff/recipientV2.entryContract.js +12 -0
- package/src/tooling/portable/handoff/recipientV2.humanOutput.js +48 -6
- package/src/tooling/portable/handoff/recipientV2.packageV1.build.js +55 -43
- package/src/tooling/portable/handoff/recipientV2.packageV1.contract.js +57 -31
- package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.helpers.js +63 -10
- package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.js +55 -16
- package/src/tooling/portable/handoff/recipientV2.packageV1.workspaceProjection.js +14 -1
- package/src/tooling/portable/handoff/recipientV2.topology.js +3 -4
- package/src/tooling/portable/handoff/recipientV2.topology.materials.js +14 -0
- package/src/tooling/portable/handoff/workspaceCarrier.manufacture.js +42 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiinex/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Shared host-neutral Tiinex implementation core for artifacts, schemas, validation, lineage, grounding, Handoffs, provenance and deterministic workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": true,
|
|
@@ -168,12 +168,12 @@
|
|
|
168
168
|
"type": "git",
|
|
169
169
|
"url": "git+https://github.com/Tiinex/core.git"
|
|
170
170
|
},
|
|
171
|
-
"gitHead": "
|
|
171
|
+
"gitHead": "d257bb3ae9e7c6361840d727ce5e359d1b6f6091",
|
|
172
172
|
"tiinexRelease": {
|
|
173
173
|
"policy": "tiinex.master-npm-release.v1",
|
|
174
|
-
"sourceCommit": "
|
|
175
|
-
"sourceTree": "
|
|
174
|
+
"sourceCommit": "d257bb3ae9e7c6361840d727ce5e359d1b6f6091",
|
|
175
|
+
"sourceTree": "002c94ff43122ac8188de9514909dfdabd348803",
|
|
176
176
|
"repository": "Tiinex/core",
|
|
177
|
-
"previousVersion": "0.
|
|
177
|
+
"previousVersion": "0.14.0"
|
|
178
178
|
}
|
|
179
179
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { sha256Base64Url } from './integrity.c14nV2.js';
|
|
2
|
+
|
|
3
|
+
export const C14N_V1_METHOD_ID = 'sha256-base64url-c14n-v1';
|
|
4
|
+
|
|
5
|
+
export function canonicalC14nV1TargetState(markdown = '') {
|
|
6
|
+
const normalized = normalizeCanonicalSource(markdown);
|
|
7
|
+
const lines = normalized.split('\n');
|
|
8
|
+
const integrityHeading = lines.findIndex((line) => line.trim() === '# Continuity Integrity');
|
|
9
|
+
const canonical = (integrityHeading < 0 ? lines : lines.slice(0, integrityHeading)).join('\n');
|
|
10
|
+
return Object.freeze({
|
|
11
|
+
state: 'computed',
|
|
12
|
+
method: C14N_V1_METHOD_ID,
|
|
13
|
+
canonical,
|
|
14
|
+
computedValue: sha256Base64Url(canonical),
|
|
15
|
+
integrityHeading
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function c14nV1TargetDigest(markdown = '') {
|
|
20
|
+
return canonicalC14nV1TargetState(markdown).computedValue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeCanonicalSource(markdown = '') {
|
|
24
|
+
return String(markdown || '')
|
|
25
|
+
.replace(/\r\n?/g, '\n')
|
|
26
|
+
.replace(/[ \t]+$/gm, '')
|
|
27
|
+
.trimEnd();
|
|
28
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { C14N_V1_METHOD_ID, c14nV1TargetDigest } from '../integrity/integrity.c14nV1.js';
|
|
2
|
+
import { C14N_V2_METHOD_ID } from '../integrity/integrity.c14nV2.js';
|
|
1
3
|
import { LineageResolutionStatus } from './lineage.model.js';
|
|
2
4
|
import { canonicalPath, canonicalToken, provenanceTargetKeysForValue } from './lineage.targetKeys.js';
|
|
3
5
|
|
|
@@ -5,57 +7,138 @@ export function verifiedIntegrityMatch(match = null) {
|
|
|
5
7
|
if (!match || match.ambiguous || match.selfReference || match.blocked) return match;
|
|
6
8
|
return Object.assign({}, match, {
|
|
7
9
|
status: LineageResolutionStatus.verified,
|
|
8
|
-
diagnostics: [lineageDiagnostic('integrity.verified', 'Loaded parent
|
|
10
|
+
diagnostics: [lineageDiagnostic('integrity.verified', 'Loaded parent integrity matches the child declaration.', { basis: 'checksum' })]
|
|
9
11
|
});
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
export function withParentIntegrityStatus(match = null,
|
|
13
|
-
if (!match || !Array.isArray(
|
|
14
|
+
export function withParentIntegrityStatus(match = null, expectedIntegrity = []) {
|
|
15
|
+
if (!match || !Array.isArray(expectedIntegrity) || !expectedIntegrity.length) return match;
|
|
14
16
|
if (match.ambiguous || match.selfReference || match.blocked) return match;
|
|
15
17
|
if (match.status === LineageResolutionStatus.verified || match.status === LineageResolutionStatus.mismatch || match.status === LineageResolutionStatus.probable) return match;
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
const expectations = normalizeIntegrityExpectations(expectedIntegrity);
|
|
19
|
+
if (!expectations.length) return match;
|
|
20
|
+
const checks = expectations.map((expectation) => verifyParentIntegrityExpectation(match, expectation));
|
|
21
|
+
const verified = checks.find((check) => check.state === 'verified');
|
|
22
|
+
if (verified) {
|
|
19
23
|
return Object.assign({}, match, {
|
|
20
|
-
status: LineageResolutionStatus.
|
|
21
|
-
diagnostics: [lineageDiagnostic('integrity.
|
|
24
|
+
status: LineageResolutionStatus.verified,
|
|
25
|
+
diagnostics: [lineageDiagnostic('integrity.verified', verified.message, { basis: verified.basis, method: verified.method, expected: verified.expected, actual: verified.actual })]
|
|
22
26
|
});
|
|
23
27
|
}
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
28
|
+
const comparable = checks.filter((check) => check.state === 'mismatch');
|
|
29
|
+
if (!comparable.length) {
|
|
26
30
|
return Object.assign({}, match, {
|
|
27
|
-
status: LineageResolutionStatus.
|
|
28
|
-
diagnostics: [lineageDiagnostic('integrity.
|
|
31
|
+
status: LineageResolutionStatus.probable,
|
|
32
|
+
diagnostics: [lineageDiagnostic('integrity.unavailable', 'Declared parent found, but the loaded parent does not expose the method-specific material required to verify the child declaration.', { basis: match.method || '', methods: expectations.map((item) => item.method).filter(Boolean).join(', ') })]
|
|
29
33
|
});
|
|
30
34
|
}
|
|
31
35
|
return Object.assign({}, match, {
|
|
32
36
|
status: LineageResolutionStatus.mismatch,
|
|
33
|
-
diagnostics:
|
|
37
|
+
diagnostics: comparable.map((check) => lineageDiagnostic('integrity.mismatch', check.message, { basis: check.basis, method: check.method, expected: check.expected, actual: check.actual }))
|
|
34
38
|
});
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
export function
|
|
41
|
+
export function parentIntegrityExpectationsForTarget(node = {}, target = '') {
|
|
38
42
|
const targetKeys = lineageTargetComparisonKeys(target);
|
|
39
43
|
if (!targetKeys.length) return [];
|
|
40
44
|
const entries = integrityEntriesForNode(node);
|
|
41
|
-
const
|
|
45
|
+
const expectations = [];
|
|
42
46
|
for (const entry of entries) {
|
|
43
47
|
const towards = String(entry.towards || '').trim();
|
|
44
48
|
if (!towards || /^self$/i.test(towards)) continue;
|
|
45
49
|
const entryKeys = lineageTargetComparisonKeys(towards);
|
|
46
50
|
if (!entryKeys.some((key) => targetKeys.includes(key))) continue;
|
|
47
51
|
const value = canonicalIntegrityValue(entry.value);
|
|
48
|
-
if (value)
|
|
52
|
+
if (!value) continue;
|
|
53
|
+
expectations.push(Object.freeze({ method: canonicalIntegrityMethod(entry.method || entry.declaredMethod || ''), value, towards }));
|
|
49
54
|
}
|
|
50
|
-
return
|
|
55
|
+
return dedupeExpectations(expectations);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function parentIntegrityValuesForTarget(node = {}, target = '') {
|
|
59
|
+
return parentIntegrityExpectationsForTarget(node, target).map((entry) => entry.value);
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
export function selfIntegrityValuesForNode(node = {}) {
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
return Array.from(new Set(selfIntegrityEntriesForNode(node).map((entry) => canonicalIntegrityValue(entry.value)).filter(Boolean)));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function selfIntegrityEntriesForNode(node = {}) {
|
|
67
|
+
return integrityEntriesForNode(node)
|
|
56
68
|
.filter((entry) => /^self$/i.test(String(entry.towards || '').trim()))
|
|
57
|
-
.map((entry) =>
|
|
58
|
-
|
|
69
|
+
.map((entry) => Object.freeze({
|
|
70
|
+
method: canonicalIntegrityMethod(entry.method || entry.declaredMethod || ''),
|
|
71
|
+
value: canonicalIntegrityValue(entry.value),
|
|
72
|
+
towards: 'self'
|
|
73
|
+
}))
|
|
74
|
+
.filter((entry) => entry.value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function verifyParentIntegrityExpectation(match = {}, expectation = {}) {
|
|
78
|
+
const method = canonicalIntegrityMethod(expectation.method || '');
|
|
79
|
+
const expected = canonicalIntegrityValue(expectation.value);
|
|
80
|
+
if (!expected) return unavailableCheck(method, expected, 'integrity-value-missing');
|
|
81
|
+
const markdown = String(match?.record?.markdown || match?.markdown || '');
|
|
82
|
+
if (method === C14N_V1_METHOD_ID) {
|
|
83
|
+
if (!markdown) return unavailableCheck(method, expected, 'target-markdown-unavailable');
|
|
84
|
+
const actual = c14nV1TargetDigest(markdown);
|
|
85
|
+
return Object.freeze({
|
|
86
|
+
state: actual === expected ? 'verified' : 'mismatch',
|
|
87
|
+
method,
|
|
88
|
+
expected,
|
|
89
|
+
actual,
|
|
90
|
+
basis: C14N_V1_METHOD_ID,
|
|
91
|
+
message: actual === expected
|
|
92
|
+
? 'Declared parent found and direct c14n-v1 canonicalization of the resolved target matches the child declaration.'
|
|
93
|
+
: 'Declared parent found, but direct c14n-v1 canonicalization of the resolved target does not match the child declaration.'
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const selfEntries = selfIntegrityEntriesForNode(match);
|
|
98
|
+
const comparableEntries = method ? selfEntries.filter((entry) => entry.method === method) : selfEntries;
|
|
99
|
+
const actualValues = Array.from(new Set(comparableEntries.map((entry) => entry.value).filter(Boolean)));
|
|
100
|
+
if (!actualValues.length) return unavailableCheck(method, expected, 'target-self-integrity-unavailable');
|
|
101
|
+
const matched = actualValues.includes(expected);
|
|
102
|
+
return Object.freeze({
|
|
103
|
+
state: matched ? 'verified' : 'mismatch',
|
|
104
|
+
method,
|
|
105
|
+
expected,
|
|
106
|
+
actual: actualValues.join(', '),
|
|
107
|
+
basis: method || match.method || 'target-self-integrity',
|
|
108
|
+
message: matched
|
|
109
|
+
? 'Declared parent found and method-compatible target self-integrity matches the child declaration.'
|
|
110
|
+
: 'Declared parent found, but method-compatible target self-integrity does not match the child declaration.'
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function unavailableCheck(method = '', expected = '', reason = '') {
|
|
115
|
+
return Object.freeze({ state: 'unavailable', method, expected, actual: '', basis: method || reason, reason, message: 'Method-specific target integrity material is unavailable.' });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function normalizeIntegrityExpectations(values = []) {
|
|
119
|
+
const out = [];
|
|
120
|
+
for (const item of values) {
|
|
121
|
+
if (item && typeof item === 'object') {
|
|
122
|
+
const value = canonicalIntegrityValue(item.value);
|
|
123
|
+
if (value) out.push(Object.freeze({ method: canonicalIntegrityMethod(item.method || item.declaredMethod || ''), value, towards: String(item.towards || '') }));
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const value = canonicalIntegrityValue(item);
|
|
127
|
+
if (value) out.push(Object.freeze({ method: '', value, towards: '' }));
|
|
128
|
+
}
|
|
129
|
+
return dedupeExpectations(out);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function dedupeExpectations(entries = []) {
|
|
133
|
+
const seen = new Set();
|
|
134
|
+
const out = [];
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
const key = `${entry.method}\u0000${entry.value}\u0000${entry.towards}`;
|
|
137
|
+
if (seen.has(key)) continue;
|
|
138
|
+
seen.add(key);
|
|
139
|
+
out.push(entry);
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
59
142
|
}
|
|
60
143
|
|
|
61
144
|
function integrityEntriesForNode(node = {}) {
|
|
@@ -116,6 +199,10 @@ export function canonicalIntegrityValue(value = '') {
|
|
|
116
199
|
return String(value || '').trim();
|
|
117
200
|
}
|
|
118
201
|
|
|
202
|
+
export function canonicalIntegrityMethod(value = '') {
|
|
203
|
+
return stripMarkdown(String(value || '').trim()).toLowerCase();
|
|
204
|
+
}
|
|
205
|
+
|
|
119
206
|
function normalizeIntegrityField(value = '') {
|
|
120
207
|
const raw = String(value || '').trim();
|
|
121
208
|
const link = raw.match(/^\[([^\]]*)\]\(([^)]+)\)$/);
|
|
@@ -130,4 +217,3 @@ function stripMarkdown(value = '') {
|
|
|
130
217
|
function lineageDiagnostic(code, message, extra = {}) {
|
|
131
218
|
return Object.freeze(Object.assign({ code, message }, extra || {}));
|
|
132
219
|
}
|
|
133
|
-
|
|
@@ -3,7 +3,7 @@ import { issueLocalPathKeysForNode, issueLocalPathMatches } from './lineage.gith
|
|
|
3
3
|
import { filterGitHubIssueCommentCandidatesForTarget, githubIssueCommentIdFromValue, githubIssueCommentIdsForNode } from './lineage.githubIssueComment.js';
|
|
4
4
|
import { filterExactGitHubIssueCandidatesForTarget } from './lineage.githubIssueTarget.js';
|
|
5
5
|
import { isDotRelativeReference, isSimpleRelativeReference, isUrlLike, relativeCandidatePath, resolveCandidateNodes } from './lineage.candidateResolution.js';
|
|
6
|
-
import { canonicalIntegrityValue, parentIntegrityValuesForTarget, selfIntegrityValuesForNode,
|
|
6
|
+
import { canonicalIntegrityValue, parentIntegrityExpectationsForTarget, parentIntegrityValuesForTarget, selfIntegrityValuesForNode, withParentIntegrityStatus } from './lineage.integrity.js';
|
|
7
7
|
import { canonicalPath, canonicalToken, githubRepoRelativePathFromUrl, provenanceTargetKeysForValue, sourceKeyFromTarget } from './lineage.targetKeys.js';
|
|
8
8
|
import { declaredParentBindingTargetValuesForNode, isSyntheticPublicationLineageNode } from './lineage.parentBinding.js';
|
|
9
9
|
import { exactUnloadedParent } from './lineage.parentAuthority.js';
|
|
@@ -22,7 +22,7 @@ export function resolveLineage(artifacts = [], options = {}) {
|
|
|
22
22
|
}
|
|
23
23
|
const traceTarget = targets.find((target) => target.kind === LineageEdgeKind.parent);
|
|
24
24
|
const originTarget = targets.find((target) => target.kind === LineageEdgeKind.origin);
|
|
25
|
-
const parentMatch = traceTarget ? resolveTarget(traceTarget.value, index, node, { expectedIntegrityValues: traceTarget.integrityValues, targetKind: LineageEdgeKind.parent }) : null;
|
|
25
|
+
const parentMatch = traceTarget ? resolveTarget(traceTarget.value, index, node, { expectedIntegrityValues: traceTarget.integrityValues, expectedIntegrity: traceTarget.integrityExpectations, targetKind: LineageEdgeKind.parent }) : null;
|
|
26
26
|
const originMatch = originTarget ? resolveTarget(originTarget.value, index, node, { targetKind: LineageEdgeKind.origin }) : null;
|
|
27
27
|
if (parentMatch?.selfReference) {
|
|
28
28
|
findings.push(createLineageFinding('lineage.parent.selfReference', 'Declared Parent Trace resolves to the declaring artifact itself; no parent edge was created.', 'warning', { nodeId: node.id, target: traceTarget.value }));
|
|
@@ -170,7 +170,10 @@ export function resolveLineage(artifacts = [], options = {}) {
|
|
|
170
170
|
}
|
|
171
171
|
function declaredTargetsFor(node = {}) {
|
|
172
172
|
const targets = [];
|
|
173
|
-
if (node.trace)
|
|
173
|
+
if (node.trace) {
|
|
174
|
+
const integrityExpectations = parentIntegrityExpectationsForTarget(node, node.trace);
|
|
175
|
+
targets.push({ kind: LineageEdgeKind.parent, value: node.trace, integrityExpectations, integrityValues: integrityExpectations.length ? integrityExpectations.map((entry) => entry.value) : parentIntegrityValuesForTarget(node, node.trace) });
|
|
176
|
+
}
|
|
174
177
|
if (node.origin) targets.push({ kind: LineageEdgeKind.origin, value: node.origin });
|
|
175
178
|
return targets;
|
|
176
179
|
}
|
|
@@ -228,23 +231,27 @@ function resolveTarget(target, index, declaringNode = null, options = {}) {
|
|
|
228
231
|
const raw = String(target || '').trim();
|
|
229
232
|
if (!raw) return null;
|
|
230
233
|
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
234
|
+
const expectedIntegrity = Array.isArray(options.expectedIntegrity) && options.expectedIntegrity.length
|
|
235
|
+
? options.expectedIntegrity
|
|
236
|
+
: Array.isArray(options.expectedIntegrityValues) ? options.expectedIntegrityValues : [];
|
|
237
|
+
const expectedIntegrityValues = expectedIntegrity.map((entry) => canonicalIntegrityValue(entry && typeof entry === 'object' ? entry.value : entry)).filter(Boolean);
|
|
237
238
|
const token = canonicalToken(raw);
|
|
238
239
|
const path = canonicalPath(raw);
|
|
239
240
|
const urlFilePath = githubRepoRelativePathFromUrl(raw);
|
|
240
241
|
const urlSourceKey = sourceKeyFromTarget(raw);
|
|
242
|
+
const targetSourceConstraint = urlSourceKey ? sourceConstraintFromTarget(raw) : null;
|
|
243
|
+
if (expectedIntegrityValues.length && !targetSourceConstraint?.ref) {
|
|
244
|
+
const integrityMatch = resolveIntegrityTarget(expectedIntegrityValues, index, declaringNode);
|
|
245
|
+
if (integrityMatch) return withParentIntegrityStatus(integrityMatch, expectedIntegrity);
|
|
246
|
+
}
|
|
247
|
+
|
|
241
248
|
const declaringConstraint = sourceConstraintFromNode(declaringNode);
|
|
242
249
|
const relative = relativeCandidatePath(raw, declaringNode);
|
|
243
250
|
const simpleRelative = isSimpleRelativeReference(raw);
|
|
244
251
|
const dotRelative = isDotRelativeReference(raw);
|
|
245
252
|
const urlLike = isUrlLike(raw);
|
|
246
253
|
const recordToken = /^record:/i.test(raw);
|
|
247
|
-
const finalize = (match) => withParentIntegrityStatus(match,
|
|
254
|
+
const finalize = (match) => withParentIntegrityStatus(match, expectedIntegrity);
|
|
248
255
|
|
|
249
256
|
const resolveDirectToken = () => {
|
|
250
257
|
const directTokenCandidates = [
|
|
@@ -297,7 +304,7 @@ function resolveTarget(target, index, declaringNode = null, options = {}) {
|
|
|
297
304
|
continue;
|
|
298
305
|
}
|
|
299
306
|
const sourceKey = sourceKeyFromTarget(binding.raw);
|
|
300
|
-
const constraint = sourceKey ? sourceConstraintFromTarget(
|
|
307
|
+
const constraint = sourceKey ? sourceConstraintFromTarget(binding.raw) : declaringConstraint;
|
|
301
308
|
const exact = exactPathMatches(binding.filePath, index, constraint, Boolean(constraint.hasConstraint));
|
|
302
309
|
const resolvedExact = resolveCandidateNodes(exact, 'declared-parent-path', declaringNode);
|
|
303
310
|
if (resolvedExact) return finalize(resolvedExact);
|
|
@@ -357,12 +364,12 @@ function resolveTarget(target, index, declaringNode = null, options = {}) {
|
|
|
357
364
|
const direct = resolveDirectToken();
|
|
358
365
|
if (direct) return finalize(direct);
|
|
359
366
|
|
|
360
|
-
const pathConstraint =
|
|
367
|
+
const pathConstraint = targetSourceConstraint || declaringConstraint;
|
|
361
368
|
const exact = exactPathMatches(path, index, pathConstraint, Boolean(pathConstraint.hasConstraint));
|
|
362
369
|
const resolvedExact = resolveCandidateNodes(exact, 'path', declaringNode);
|
|
363
370
|
if (resolvedExact) return finalize(resolvedExact);
|
|
364
371
|
|
|
365
|
-
const suffixConstraint =
|
|
372
|
+
const suffixConstraint = targetSourceConstraint || declaringConstraint;
|
|
366
373
|
const suffixCandidates = [
|
|
367
374
|
['path-suffix', findPathSuffixMatches(path, index.byPath, suffixConstraint, Boolean(urlSourceKey))],
|
|
368
375
|
['source-path-suffix', findPathSuffixMatches(path, index.bySourcePath, suffixConstraint, Boolean(urlSourceKey))]
|
|
@@ -384,5 +391,5 @@ function resolveIntegrityTarget(expectedIntegrityValues = [], index = {}, declar
|
|
|
384
391
|
}
|
|
385
392
|
const resolved = resolveCandidateNodes(nodes, 'integrity-self-hash', declaringNode);
|
|
386
393
|
if (!resolved || resolved.ambiguous || resolved.selfReference || resolved.blocked) return resolved;
|
|
387
|
-
return
|
|
394
|
+
return resolved;
|
|
388
395
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { canonicalPath, normalizeRef, normalizeRepoKey } from './lineage.targetKeys.js';
|
|
1
|
+
import { canonicalPath, githubFileIdentityFromUrl, normalizeRef, normalizeRepoKey } from './lineage.targetKeys.js';
|
|
2
2
|
|
|
3
3
|
export function exactPathMatches(path, index, constraint = {}, strictSource = false) {
|
|
4
4
|
const source = [
|
|
@@ -27,18 +27,47 @@ export function findPathSuffixMatches(targetPath, pathIndex = new Map(), constra
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function sourceConstraintFromNode(node = {}) {
|
|
30
|
-
const
|
|
31
|
-
const
|
|
30
|
+
const record = node?.record || node || {};
|
|
31
|
+
const source = record.source || {};
|
|
32
|
+
const provenance = sourceIdentityFromRecord(record);
|
|
32
33
|
const sourceId = String(source.id || '').trim();
|
|
33
|
-
|
|
34
|
-
const repo = normalizeRepoKey(source.repo || source.repository || source.config?.repo || '');
|
|
35
|
-
const ref = normalizeRef(source.ref || source.config?.ref || '');
|
|
36
|
-
|
|
34
|
+
const adapterId = String(source.adapterId || source.adapter || provenance.adapterId || '').trim().toLowerCase();
|
|
35
|
+
const repo = normalizeRepoKey(source.repo || source.repository || source.config?.repo || provenance.repo || '');
|
|
36
|
+
const ref = normalizeRef(source.commit || source.ref || source.config?.commit || source.config?.ref || provenance.ref || '');
|
|
37
|
+
const isLocal = adapterId === 'local' || sourceId === 'local' || source.kind === 'local-session';
|
|
38
|
+
if (isLocal && !repo && !ref) return { hasConstraint: false, sourceId: '', repo: '', ref: '', adapterId: '', exactRef: false };
|
|
39
|
+
return { hasConstraint: Boolean(sourceId || repo || adapterId || ref), sourceId, repo, ref, adapterId, exactRef: Boolean(ref) };
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
export function sourceConstraintFromTarget(
|
|
40
|
-
const
|
|
41
|
-
return { hasConstraint:
|
|
42
|
+
export function sourceConstraintFromTarget(value = '') {
|
|
43
|
+
const identity = githubFileIdentityFromUrl(value);
|
|
44
|
+
if (identity.repo) return { hasConstraint: true, repo: identity.repo, ref: identity.ref, sourceId: '', adapterId: 'github', exactRef: Boolean(identity.ref) };
|
|
45
|
+
const key = normalizeRepoKey(value);
|
|
46
|
+
return { hasConstraint: Boolean(key), repo: key, ref: '', sourceId: '', adapterId: key ? 'github' : '', exactRef: false };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function sourceIdentityFromRecord(record = {}) {
|
|
50
|
+
const sourceTarget = record.sourceTarget || {};
|
|
51
|
+
const snapshot = record.snapshot || {};
|
|
52
|
+
const target = snapshot.target || {};
|
|
53
|
+
const values = [
|
|
54
|
+
record.recoveredFromUrl,
|
|
55
|
+
record.sourceOrigin,
|
|
56
|
+
record.rawUrl,
|
|
57
|
+
record.browseUrl,
|
|
58
|
+
sourceTarget.inputTarget,
|
|
59
|
+
sourceTarget.rawUrl,
|
|
60
|
+
sourceTarget.browseUrl,
|
|
61
|
+
snapshot.sourceUrl,
|
|
62
|
+
target.canonicalUrl,
|
|
63
|
+
target.html_url,
|
|
64
|
+
target.url
|
|
65
|
+
];
|
|
66
|
+
for (const value of values) {
|
|
67
|
+
const identity = githubFileIdentityFromUrl(value);
|
|
68
|
+
if (identity.repo) return { ...identity, adapterId: 'github' };
|
|
69
|
+
}
|
|
70
|
+
return { repo: '', ref: '', path: '', adapterId: '' };
|
|
42
71
|
}
|
|
43
72
|
|
|
44
73
|
function filterBySource(nodes = [], constraint = {}, strictSource = false) {
|
|
@@ -46,7 +75,7 @@ function filterBySource(nodes = [], constraint = {}, strictSource = false) {
|
|
|
46
75
|
if (!constraint?.hasConstraint) return items;
|
|
47
76
|
const filtered = items.filter((node) => nodeMatchesSourceConstraint(node, constraint));
|
|
48
77
|
if (filtered.length) return filtered;
|
|
49
|
-
if (strictSource && items.length && items.every((node) => !sourceConstraintFromNode(node).hasConstraint)) return items;
|
|
78
|
+
if (strictSource && !constraint.ref && items.length && items.every((node) => !sourceConstraintFromNode(node).hasConstraint)) return items;
|
|
50
79
|
return [];
|
|
51
80
|
}
|
|
52
81
|
|
|
@@ -55,7 +84,7 @@ function nodeMatchesSourceConstraint(node = {}, constraint = {}) {
|
|
|
55
84
|
if (constraint.sourceId && candidate.sourceId && constraint.sourceId !== candidate.sourceId) return false;
|
|
56
85
|
if (constraint.adapterId && candidate.adapterId && constraint.adapterId !== candidate.adapterId) return false;
|
|
57
86
|
if (constraint.repo && candidate.repo !== constraint.repo) return false;
|
|
58
|
-
if (constraint.ref && candidate.ref
|
|
87
|
+
if (constraint.ref && candidate.ref !== constraint.ref) return false;
|
|
59
88
|
if (constraint.repo && !candidate.repo) return false;
|
|
60
89
|
if (constraint.adapterId && !candidate.adapterId) return false;
|
|
61
90
|
return true;
|
|
@@ -31,14 +31,29 @@ export function provenanceTargetKeysForValue(value = '') {
|
|
|
31
31
|
}
|
|
32
32
|
return keys;
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
|
|
35
|
+
export function githubFileIdentityFromUrl(value = '') {
|
|
36
|
+
try {
|
|
37
|
+
const url = new URL(String(value || '').trim());
|
|
38
|
+
const host = url.hostname.toLowerCase();
|
|
39
|
+
const parts = url.pathname.split('/').filter(Boolean);
|
|
40
|
+
if (host === 'raw.githubusercontent.com' && parts.length >= 4) {
|
|
41
|
+
return Object.freeze({ repo: normalizeRepoKey(`${parts[0]}/${parts[1]}`), ref: normalizeRef(parts[2]), path: normalizePathParts(parts.slice(3)) });
|
|
42
|
+
}
|
|
43
|
+
if ((host === 'github.com' || host.endsWith('.github.com')) && parts.length >= 5 && parts[2] === 'blob') {
|
|
44
|
+
return Object.freeze({ repo: normalizeRepoKey(`${parts[0]}/${parts[1]}`), ref: normalizeRef(parts[3]), path: normalizePathParts(parts.slice(4)) });
|
|
45
|
+
}
|
|
38
46
|
} catch (_) {}
|
|
39
|
-
return '';
|
|
47
|
+
return Object.freeze({ repo: '', ref: '', path: '' });
|
|
40
48
|
}
|
|
49
|
+
|
|
50
|
+
export function githubRepoRelativePathFromUrl(value = '') {
|
|
51
|
+
return githubFileIdentityFromUrl(value).path;
|
|
52
|
+
}
|
|
53
|
+
|
|
41
54
|
export function sourceKeyFromTarget(value = '') {
|
|
55
|
+
const identity = githubFileIdentityFromUrl(value);
|
|
56
|
+
if (identity.repo) return identity.repo;
|
|
42
57
|
try {
|
|
43
58
|
const url = new URL(String(value || ''));
|
|
44
59
|
const parts = url.pathname.split('/').filter(Boolean);
|
|
@@ -53,7 +68,7 @@ export function normalizeRepoKey(value = '') {
|
|
|
53
68
|
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : '';
|
|
54
69
|
}
|
|
55
70
|
export function normalizeRef(value = '') {
|
|
56
|
-
return String(value || '').trim()
|
|
71
|
+
return String(value || '').trim();
|
|
57
72
|
}
|
|
58
73
|
export function canonicalToken(value = '') {
|
|
59
74
|
return String(value || '').trim().replace(/^record:/i, 'record:').replace(/\s+/g, '');
|
|
@@ -70,8 +85,12 @@ export function canonicalPath(value = '') {
|
|
|
70
85
|
raw = url.pathname.replace(/^\/+/, '');
|
|
71
86
|
} catch (e) {}
|
|
72
87
|
}
|
|
88
|
+
return normalizePathParts(raw.replace(/\\/g, '/').split('/'));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalizePathParts(parts = []) {
|
|
73
92
|
const out = [];
|
|
74
|
-
for (const part of
|
|
93
|
+
for (const part of parts) {
|
|
75
94
|
if (!part || part === '.') continue;
|
|
76
95
|
if (part === '..') out.pop();
|
|
77
96
|
else out.push(part);
|
|
@@ -2,6 +2,7 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { prepareNodeHandoffManufacturingInput } from '../node/handoff.manufacture.js';
|
|
4
4
|
import { prepareNodeWorkspaceCarrierManufacturingInput } from '../node/workspaceCarrier.manufacture.js';
|
|
5
|
+
import { prepareNodeBootstrapCarrierManufacturingInput } from '../node/bootstrapCarrier.manufacture.js';
|
|
5
6
|
import { projectHandoffHumanOutput } from '../../handoff/carrierProjection.js';
|
|
6
7
|
import { writePortableRuntimePackageZip } from '../../output/node.zip.js';
|
|
7
8
|
import { writeRecipientFacingV2PackageZip } from '../../output/recipientV2.zip.js';
|
|
@@ -16,8 +17,9 @@ export async function prepareHandoffManufactureCliCommand(parsed = {}, runtime =
|
|
|
16
17
|
const flags = parsed.flags || {};
|
|
17
18
|
const workspaceRoot = flags.workspace || parsed.positionals?.[0] || '.';
|
|
18
19
|
const carrierMode = String(flags['carrier-mode'] || 'handoff').trim().toLowerCase();
|
|
19
|
-
if (!['handoff', 'workspace'].includes(carrierMode)) throw new Error(`portable.cli.handoff-carrier.carrier-mode.invalid:${carrierMode}`);
|
|
20
|
+
if (!['handoff', 'workspace', 'bootstrap'].includes(carrierMode)) throw new Error(`portable.cli.handoff-carrier.carrier-mode.invalid:${carrierMode}`);
|
|
20
21
|
if (carrierMode === 'workspace') return prepareWorkspaceCarrierCliCommand(flags, workspaceRoot, runtime);
|
|
22
|
+
if (carrierMode === 'bootstrap') return prepareBootstrapCarrierCliCommand(flags, runtime);
|
|
21
23
|
const continuationState = parsed.surfaceCommand === 'handoff'
|
|
22
24
|
? await readGroundContinuationState(workspaceRoot)
|
|
23
25
|
: {};
|
|
@@ -146,13 +148,15 @@ export async function prepareHandoffManufactureCliCommand(parsed = {}, runtime =
|
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
export async function materializeHandoffManufactureCliOutput(result = {}, flags = {}) {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
+
const carrierMode = String(result.carrierProjection?.mode || '');
|
|
152
|
+
const workspaceMode = carrierMode === 'workspace';
|
|
153
|
+
const bootstrapMode = carrierMode === 'bootstrap';
|
|
154
|
+
let humanOutput = workspaceMode ? projectWorkspaceCarrierHumanOutput(result, flags) : bootstrapMode ? projectBootstrapCarrierHumanOutput(result, flags) : projectHandoffHumanOutput({
|
|
151
155
|
projection: result.carrierProjection || {},
|
|
152
156
|
route: flags.route || '',
|
|
153
157
|
collisionInstance: flags['collision-instance'] || 1
|
|
154
158
|
});
|
|
155
|
-
if (
|
|
159
|
+
if (result.bundle?.transportFormat) humanOutput = projectRecipientV2HumanOutput(humanOutput, result.inspection || {});
|
|
156
160
|
const wantsWrite = Boolean(flags.output || flags['output-dir']);
|
|
157
161
|
const blocked = result.status === 'blocked' || result.transportExecutable === false || Number(result.findingSummary?.counts?.error || 0) > 0;
|
|
158
162
|
if (!wantsWrite || blocked) return summarizeHandoffManufactureCliOutput(result, {}, humanOutput, null);
|
|
@@ -163,7 +167,6 @@ export async function materializeHandoffManufactureCliOutput(result = {}, flags
|
|
|
163
167
|
const writeReceipt = writeBundle?.transportFormat
|
|
164
168
|
? await writeRecipientFacingV2PackageZip(writeBundle, target, writeBundle === result.bundle ? { inspection: result.inspection } : {})
|
|
165
169
|
: await writePortableRuntimePackageZip(writeBundle, target);
|
|
166
|
-
if (workspaceMode && flags['transport-text']) throw new Error('portable.cli.workspace-carrier.transport-text.unavailable');
|
|
167
170
|
const transportTextReceipt = flags['transport-text'] ? await writeTransportTextSidecar(humanOutput, target, flags['transport-text']) : null;
|
|
168
171
|
return summarizeHandoffManufactureCliOutput(result, writeReceipt, humanOutput, transportTextReceipt);
|
|
169
172
|
}
|
|
@@ -174,6 +177,7 @@ async function prepareWorkspaceCarrierCliCommand(flags = {}, workspaceRoot = '.'
|
|
|
174
177
|
const expectedToolingBootstrap = await readOptionalJson(flags['tooling-bootstrap-manifest']);
|
|
175
178
|
const workspaceDescriptorValue = await readOptionalJson(flags['workspace-roots'] || flags['workspace-descriptors']);
|
|
176
179
|
const workspaceTargetValue = await readOptionalJson(flags['workspace-targets']);
|
|
180
|
+
const workspaceScopeValue = await readOptionalJson(flags['workspace-scopes']);
|
|
177
181
|
const additionalWorkspaces = [...splitFlag(flags['additional-workspaces']), ...descriptorArray(workspaceDescriptorValue, 'workspaces')];
|
|
178
182
|
const verifyRoundtrip = !flags['no-roundtrip'];
|
|
179
183
|
const carrierProfile = selectCarrierProfile({ operator: operatorCarrierProfile, runtime: runtime.defaultCarrierProfile || null });
|
|
@@ -185,6 +189,8 @@ async function prepareWorkspaceCarrierCliCommand(flags = {}, workspaceRoot = '.'
|
|
|
185
189
|
workspaceTitle: flags['workspace-title'] || flags.title || '',
|
|
186
190
|
workspaceTargetPath: flags['workspace-target'] || flags['workspace-artifact'] || '',
|
|
187
191
|
workspaceTargets: workspaceTargetValue,
|
|
192
|
+
workspaceScopes: descriptorArray(workspaceScopeValue, 'scopes').length ? descriptorArray(workspaceScopeValue, 'scopes') : workspaceScopeValue,
|
|
193
|
+
materialRepresentationWorkspaceIds: splitFlag(flags['material-representation-workspaces'] || flags['generic-material-workspaces']),
|
|
188
194
|
toolingBootstrap: flags['tooling-bootstrap'] || 'embedded',
|
|
189
195
|
expectedToolingBootstrap,
|
|
190
196
|
maxFiles: flags['max-files'],
|
|
@@ -198,6 +204,37 @@ async function prepareWorkspaceCarrierCliCommand(flags = {}, workspaceRoot = '.'
|
|
|
198
204
|
return { input, options: { verifyRoundtrip, packageInput: { builtAt: flags['built-at'] || undefined } } };
|
|
199
205
|
}
|
|
200
206
|
|
|
207
|
+
async function prepareBootstrapCarrierCliCommand(flags = {}, runtime = {}) {
|
|
208
|
+
if (flags.handoff || flags.route || flags.routes || flags['handoff-routes'] || flags['workspace-routes'] || flags.workspace || flags['workspace-target'] || flags['workspace-targets'] || flags['workspace-roots'] || flags['workspace-descriptors']) throw new Error('portable.cli.bootstrap-carrier.source-material-or-route.forbidden');
|
|
209
|
+
const operatorCarrierProfile = await readOptionalJson(flags['carrier-profile']);
|
|
210
|
+
const expectedToolingBootstrap = await readOptionalJson(flags['tooling-bootstrap-manifest']);
|
|
211
|
+
const verifyRoundtrip = !flags['no-roundtrip'];
|
|
212
|
+
const carrierProfile = selectCarrierProfile({ operator: operatorCarrierProfile, runtime: runtime.defaultCarrierProfile || null });
|
|
213
|
+
const input = await prepareNodeBootstrapCarrierManufacturingInput({
|
|
214
|
+
toolingBootstrap: flags['tooling-bootstrap'] || 'embedded', expectedToolingBootstrap, bootstrapMaxFiles: flags['bootstrap-max-files'], verifyRoundtrip, createdAt: flags['built-at'] || undefined,
|
|
215
|
+
carrierLineage: Object.freeze({ ...initialHandoffCarrierLineage(), checkpointKind: 'progression', majorReason: '' }), carrierProfile
|
|
216
|
+
}, runtime);
|
|
217
|
+
return { input, options: { verifyRoundtrip, packageInput: { builtAt: flags['built-at'] || undefined } } };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function projectBootstrapCarrierHumanOutput(result = {}, flags = {}) {
|
|
221
|
+
const projection = result.carrierProjection || {};
|
|
222
|
+
const ready = result.status === 'ready' && projection.status === 'ready' && projection.mode === 'bootstrap' && (projection.routes || []).length === 0 && (projection.workspaces || []).length === 0;
|
|
223
|
+
const dimension = String(projection.lineage?.dimension || '001');
|
|
224
|
+
const projectedFilename = String(flags['projected-filename'] || flags.projectedFilename || '').trim();
|
|
225
|
+
const filename = projectedFilename || `tiinex-${dimension}.handoff-package.zip`;
|
|
226
|
+
if (filename !== filename.trim() || !filename.endsWith('.handoff-package.zip') || /[<>:"/\\|?*\x00-\x1f\x7f]/.test(filename) || /[. ]$/.test(filename) || /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(filename) || new TextEncoder().encode(filename).byteLength > 255) throw new Error('portable.cli.bootstrap-carrier.filename.invalid');
|
|
227
|
+
return Object.freeze({
|
|
228
|
+
schema: 'tiinex.portable.handoff-human-output.v1', status: ready ? 'ready' : 'blocked',
|
|
229
|
+
primary: ready ? Object.freeze({ kind: 'bootstrap-package', filename, dimension, parentDimension: String(projection.lineage?.parentDimension || ''), checkpointKind: String(projection.lineage?.checkpointKind || ''), routeId: '', workspaceId: '', workspaceRelativeHandoffPath: '', collisionInstance: 1, singleHumanTransportChoice: true }) : null,
|
|
230
|
+
normalInlineRouting: ready ? Object.freeze({ kind: 'transport-text', content: '', normalEmission: true, requiredForHumanCompletion: true, placement: 'adjacent-to-primary', authority: 'none' }) : null, sharedRouting: null,
|
|
231
|
+
presentation: Object.freeze({ kind: 'bootstrap-only-carrier', label: 'Bootstrap carrier', authority: 'none', recipientLabel: '' }),
|
|
232
|
+
normalEmissionBoundary: Object.freeze({ allowed: Object.freeze(['package-file', 'generic-start-transport-text']), forbidden: Object.freeze(['workspace-label', 'route-specific-continue-from', 'recipient-label', 'holder-label', 'current-work-label']) }),
|
|
233
|
+
fallbackTransportText: ready ? Object.freeze({ supported: true, filename: filename.replace(/\.handoff-package\.zip$/i, '.transport.txt'), content: '', normalEmission: false, requiredForHumanCompletion: false, authority: 'none' }) : null, selectedRoute: null, findings: Object.freeze([]),
|
|
234
|
+
boundary: 'Bootstrap-only carrier output projection. Generic Start transport text only; no Workspace, Handoff route, recipient, holder, Role, or work projection exists.'
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
201
238
|
function projectWorkspaceCarrierHumanOutput(result = {}, flags = {}) {
|
|
202
239
|
const projection = result.carrierProjection || {};
|
|
203
240
|
const ready = result.status === 'ready' && projection.status === 'ready' && projection.mode === 'workspace' && (projection.routes || []).length === 0;
|
|
@@ -213,11 +250,11 @@ function projectWorkspaceCarrierHumanOutput(result = {}, flags = {}) {
|
|
|
213
250
|
schema: 'tiinex.portable.handoff-human-output.v1',
|
|
214
251
|
status: ready ? 'ready' : 'blocked',
|
|
215
252
|
primary: ready ? Object.freeze({ kind: 'workspace-package', filename, dimension, parentDimension: String(projection.lineage?.parentDimension || ''), checkpointKind: String(projection.lineage?.checkpointKind || ''), routeId: '', workspaceId: '', workspaceRelativeHandoffPath: '', collisionInstance: 1, singleHumanTransportChoice: true }) : null,
|
|
216
|
-
normalInlineRouting: null, sharedRouting: null,
|
|
217
|
-
presentation: Object.freeze({ kind: 'pointerless-workspace-carrier', label: 'Workspace carrier', authority: 'none' }),
|
|
218
|
-
normalEmissionBoundary: Object.freeze({ allowed: Object.freeze(['package-file']), forbidden: Object.freeze(['
|
|
219
|
-
fallbackTransportText: null, selectedRoute: null, findings: Object.freeze([]),
|
|
220
|
-
boundary: 'Pointerless Workspace-carrier output projection
|
|
253
|
+
normalInlineRouting: ready ? Object.freeze({ kind: 'transport-text', content: '', normalEmission: true, requiredForHumanCompletion: true, placement: 'adjacent-to-primary', authority: 'none' }) : null, sharedRouting: null,
|
|
254
|
+
presentation: Object.freeze({ kind: 'pointerless-workspace-carrier', label: 'Workspace carrier', authority: 'none', recipientLabel: '' }),
|
|
255
|
+
normalEmissionBoundary: Object.freeze({ allowed: Object.freeze(['package-file', 'generic-start-transport-text']), forbidden: Object.freeze(['route-specific-continue-from', 'recipient-label-from-material']) }),
|
|
256
|
+
fallbackTransportText: ready ? Object.freeze({ supported: true, filename: filename.replace(/\.handoff-package\.zip$/i, '.transport.txt'), content: '', normalEmission: false, requiredForHumanCompletion: false, authority: 'none' }) : null, selectedRoute: null, findings: Object.freeze([]),
|
|
257
|
+
boundary: 'Pointerless Workspace-carrier output projection. Generic Start transport text is permitted; Handoff Continue-from and recipient projection remain absent.'
|
|
221
258
|
});
|
|
222
259
|
}
|
|
223
260
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { buildToolingBootstrapTransportFiles } from './handoff.manufacture.bootstrap.js';
|
|
2
|
+
import { normalizeHandoffCarrierLineage } from '../../handoff/carrierLineage.js';
|
|
3
|
+
import { normalizeHandoffCarrierProfile } from '../../handoff/carrierProfile.js';
|
|
4
|
+
|
|
5
|
+
export async function prepareNodeBootstrapCarrierManufacturingInput(input = {}, options = {}) {
|
|
6
|
+
const toolingBootstrap = await buildToolingBootstrapTransportFiles({
|
|
7
|
+
delivery: input.toolingBootstrap || input.bootstrapDelivery || 'embedded',
|
|
8
|
+
runtimeRoot: input.runtimeRoot || options.runtimeRoot,
|
|
9
|
+
expected: input.expectedToolingBootstrap || null,
|
|
10
|
+
maxFiles: input.bootstrapMaxFiles || options.bootstrapMaxFiles
|
|
11
|
+
});
|
|
12
|
+
return Object.freeze({
|
|
13
|
+
carrierMode: 'bootstrap', createdAt: String(input.createdAt || ''), workspaceMaterializations: Object.freeze([]), workspaceTargets: Object.freeze([]),
|
|
14
|
+
additionalTransportFiles: toolingBootstrap.files,
|
|
15
|
+
carrierLineage: normalizeHandoffCarrierLineage(input.carrierLineage || null), carrierProfile: normalizeHandoffCarrierProfile(input.carrierProfile || null),
|
|
16
|
+
toolingBootstrap: toolingBootstrap.summary, manufacturingEvidence: Object.freeze({ toolingBootstrap: toolingBootstrap.summary }), verifyRoundtrip: input.verifyRoundtrip !== false
|
|
17
|
+
});
|
|
18
|
+
}
|