@tiinex/core 0.14.0 → 0.16.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 +7 -6
- 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.command-input.js +11 -0
- package/src/tooling/portable/adapters/cli/cli.ground-materialize.js +2 -0
- package/src/tooling/portable/adapters/cli/cli.handoff-manufacture.js +4 -1
- package/src/tooling/portable/adapters/cli/cli.help.js +1 -1
- package/src/tooling/portable/adapters/cli/cli.land.js +23 -1
- package/src/tooling/portable/adapters/cli/cli.run.js +1 -1
- package/src/tooling/portable/adapters/node/handoff.manufacture.js +9 -1
- package/src/tooling/portable/handoff/carrierProjection.js +2 -2
- package/src/tooling/portable/handoff/carrierProjection.routeQualification.js +1 -0
- package/src/tooling/portable/handoff/delegationReturnReservation.js +101 -0
- package/src/tooling/portable/handoff/manufacture.js +7 -1
- package/src/tooling/portable/handoff/materialClosure.archiveV2.js +1 -1
- package/src/tooling/portable/handoff/recipientV2.artifactInspection.js +11 -0
- package/src/tooling/portable/handoff/recipientV2.inspect.projection.js +7 -3
- package/src/tooling/portable/handoff/recipientV2.packageV1.build.js +3 -2
- package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.helpers.js +7 -1
- package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.js +15 -2
- package/src/tooling/portable/handoff/recipientV2.pointer.js +2 -0
- package/src/tooling/portable/handoff/recoveryAcceptanceAudit.js +145 -0
- package/src/tooling/portable/handoff/workspaceLandingPlan.js +110 -13
- package/src/tooling/portable/operation.catalog.package.js +9 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiinex/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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,
|
|
@@ -136,7 +136,8 @@
|
|
|
136
136
|
"./node/release-policy": "./src/release/policy.mjs",
|
|
137
137
|
"./tooling/portable/source/sourceEligibility.js": "./src/tooling/portable/source/sourceEligibility.js",
|
|
138
138
|
"./tooling/portable/comparison/sourceFrontierComparison.js": "./src/tooling/portable/comparison/sourceFrontierComparison.js",
|
|
139
|
-
"./tooling/portable/adapters/node/sourceFrontierComparison.js": "./src/tooling/portable/adapters/node/sourceFrontierComparison.js"
|
|
139
|
+
"./tooling/portable/adapters/node/sourceFrontierComparison.js": "./src/tooling/portable/adapters/node/sourceFrontierComparison.js",
|
|
140
|
+
"./tooling/portable/handoff/recoveryAcceptanceAudit.js": "./src/tooling/portable/handoff/recoveryAcceptanceAudit.js"
|
|
140
141
|
},
|
|
141
142
|
"files": [
|
|
142
143
|
"src",
|
|
@@ -168,12 +169,12 @@
|
|
|
168
169
|
"type": "git",
|
|
169
170
|
"url": "git+https://github.com/Tiinex/core.git"
|
|
170
171
|
},
|
|
171
|
-
"gitHead": "
|
|
172
|
+
"gitHead": "4da833bc6d123ea2b37acc9bc634c097afa0a91d",
|
|
172
173
|
"tiinexRelease": {
|
|
173
174
|
"policy": "tiinex.master-npm-release.v1",
|
|
174
|
-
"sourceCommit": "
|
|
175
|
-
"sourceTree": "
|
|
175
|
+
"sourceCommit": "4da833bc6d123ea2b37acc9bc634c097afa0a91d",
|
|
176
|
+
"sourceTree": "4143fd68b602b898bcdc18383b506f28a560a3ce",
|
|
176
177
|
"repository": "Tiinex/core",
|
|
177
|
-
"previousVersion": "0.
|
|
178
|
+
"previousVersion": "0.15.0"
|
|
178
179
|
}
|
|
179
180
|
}
|
|
@@ -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);
|
|
@@ -141,6 +141,17 @@ export async function commandInput(parsed, runtime = {}) {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
if (parsed.command === 'compare-source-frontiers') return prepareSourceFrontierComparisonCliInput(parsed, flags);
|
|
144
|
+
if (parsed.command === 'audit-recovery-acceptance') {
|
|
145
|
+
const basisPath = String(flags.basis || parsed.positionals[0] || '').trim();
|
|
146
|
+
const candidatePath = String(flags.candidate || parsed.positionals[1] || '').trim();
|
|
147
|
+
if (!basisPath || !candidatePath) throw new Error('portable.cli.recovery-acceptance.basis-candidate-required');
|
|
148
|
+
const [basis, candidate, expectedValue] = await Promise.all([
|
|
149
|
+
loadNodePortableInput([basisPath], { maxFiles: flags['max-carrier-files'] || 10000, maxTextBytes: flags['max-text-bytes'] || 16 * 1024 * 1024 }),
|
|
150
|
+
loadNodePortableInput([candidatePath], { maxFiles: flags['max-carrier-files'] || 10000, maxTextBytes: flags['max-text-bytes'] || 16 * 1024 * 1024 }),
|
|
151
|
+
readOptionalJson(flags['expected-removals'])
|
|
152
|
+
]);
|
|
153
|
+
return { input: { basis, candidate, workspaceIds: splitFlag(flags.workspaces || flags['workspace-ids']), expectedRemovals: expectedValue.expectedRemovals || expectedValue || {} }, options: {} };
|
|
154
|
+
}
|
|
144
155
|
if (parsed.command === 'prove-source-reconciliation') {
|
|
145
156
|
const dispositions = await readOptionalJson(flags.dispositions || flags['disposition-file']);
|
|
146
157
|
return prepareSourceFrontierReconciliationCliInput(flags, dispositions);
|
|
@@ -50,6 +50,8 @@ export async function materializeGroundWorkspaceCliOutput(result = {}, input = {
|
|
|
50
50
|
workspaceTarget: String(workspaceInspection.sourceWorkspaceTargetInnerPath || ''),
|
|
51
51
|
roleLabel: String(result?.authority?.role?.label || ''),
|
|
52
52
|
returnOutputDir: path.dirname(path.resolve(String(input.packageSourcePath || outputDir))),
|
|
53
|
+
returnPackageCarrierKind: String((inspection.routes || []).find((item) => String(item.pointerPath || '') === String(result?.authority?.route?.pointerPath || input.route || ''))?.returnCarrierReservation?.carrierKind || ''),
|
|
54
|
+
returnPackageSiblingIndex: String((inspection.routes || []).find((item) => String(item.pointerPath || '') === String(result?.authority?.route?.pointerPath || input.route || ''))?.returnCarrierReservation?.siblingIndex || ''),
|
|
53
55
|
boundary: 'Runtime-only continuation state carried forward from one qualified ground --continue receipt. It is excluded from canonical Workspace manufacture and is not semantic authority.'
|
|
54
56
|
});
|
|
55
57
|
const continuationStatePath = path.join(outputDir, '.tiinex', 'continuation.json');
|
|
@@ -26,6 +26,7 @@ export async function prepareHandoffManufactureCliCommand(parsed = {}, runtime =
|
|
|
26
26
|
const handoffPath = flags.handoff || parsed.positionals?.[1] || continuationState.returnHandoffPath || '';
|
|
27
27
|
if (!flags['workspace-id'] && continuationState.workspaceId) flags['workspace-id'] = continuationState.workspaceId;
|
|
28
28
|
if (!flags['workspace-target'] && continuationState.workspaceTarget) flags['workspace-target'] = continuationState.workspaceTarget;
|
|
29
|
+
if (!flags['package-sibling-index'] && continuationState.returnPackageSiblingIndex) flags['package-sibling-index'] = continuationState.returnPackageSiblingIndex;
|
|
29
30
|
if (!flags['package-parent'] && continuationState.packageParentPath) flags['package-parent'] = continuationState.packageParentPath;
|
|
30
31
|
if (!flags.route && handoffPath) flags.route = handoffPath;
|
|
31
32
|
if (!flags.output && !flags['output-dir'] && parsed.surfaceCommand === 'handoff' && continuationState.returnOutputDir) flags['output-dir'] = continuationState.returnOutputDir;
|
|
@@ -135,7 +136,9 @@ export async function prepareHandoffManufactureCliCommand(parsed = {}, runtime =
|
|
|
135
136
|
packageParentWorkspaceIds,
|
|
136
137
|
packageParentWorkspaceAliases,
|
|
137
138
|
reconciliationProof,
|
|
138
|
-
requireReconciliationProof
|
|
139
|
+
requireReconciliationProof,
|
|
140
|
+
returnPackageSiblingIndex: flags['return-package-sibling-index'],
|
|
141
|
+
returnPackageMajor: Boolean(flags['return-package-major'])
|
|
139
142
|
}, runtime);
|
|
140
143
|
return {
|
|
141
144
|
input,
|
|
@@ -83,7 +83,7 @@ function commonCommandHelp(command, surfaceCommand) {
|
|
|
83
83
|
'',
|
|
84
84
|
`${command} handoff <workspace-dir>`,
|
|
85
85
|
'',
|
|
86
|
-
'Infers the latest qualified authored Handoff, selected Workspace identity/target, received package parent as carrier-lineage evidence, canonical projected filename, and return output directory. A non-major continuation that writes a carrier must supply an explicitly coordinated `--package-sibling-index <1..9999>`; Tooling will not silently discover the next sibling because isolated runtimes cannot prove a globally unique reservation. Distinct explicit sibling indexes preserve same-Major parallelism, while byte-identical duplicate transport at one exact output path is idempotent and divergent bytes fail closed. It does not implicitly carry sibling Workspaces from the received package; advanced manufacture must select exact reusable parent snapshots with `--package-parent-workspaces <id,...|all>`. Recovery/integration manufacture can bind the reconciled byte proof with `--require-reconciliation-proof --reconciliation-proof <full-reconcile-receipt.json>`; missing, non-ready, edited, or source-stale proof blocks package output. The default receipt keeps output identity, routing text, closure/workspace qualification, verification, and actionable findings compact; add `--full` for the complete manufacture receipt. Normal operator completion is exactly one Handoff package plus the adjacent exact routing text. In markdown-capable hosts render that routing in a fenced code block; do not emit canonical Workspace Evidence/Handoff markdown as additional loose transport files. Runtime-only `.tiinex` state is excluded from canonical manufacture.',
|
|
86
|
+
'Infers the latest qualified authored Handoff, selected Workspace identity/target, received package parent as carrier-lineage evidence, canonical projected filename, and return output directory. When the outgoing Handoff declares `Signal Kind: return`, delegation manufacture is transport-not-ready unless the delegator supplies `--return-package-sibling-index <1..9999>` for the expected non-Major return, or explicitly declares `--return-package-major`; the qualified reservation is carried in the route pointer and later continuation state. A non-major continuation that writes a carrier must supply an explicitly coordinated `--package-sibling-index <1..9999>`; Tooling will not silently discover the next sibling because isolated runtimes cannot prove a globally unique reservation. Distinct explicit sibling indexes preserve same-Major parallelism, while byte-identical duplicate transport at one exact output path is idempotent and divergent bytes fail closed. It does not implicitly carry sibling Workspaces from the received package; advanced manufacture must select exact reusable parent snapshots with `--package-parent-workspaces <id,...|all>`. Recovery/integration manufacture can bind the reconciled byte proof with `--require-reconciliation-proof --reconciliation-proof <full-reconcile-receipt.json>`; missing, non-ready, edited, or source-stale proof blocks package output. The default receipt keeps output identity, routing text, closure/workspace qualification, verification, and actionable findings compact; add `--full` for the complete manufacture receipt. Normal operator completion is exactly one Handoff package plus the adjacent exact routing text. In markdown-capable hosts render that routing in a fenced code block; do not emit canonical Workspace Evidence/Handoff markdown as additional loose transport files. Runtime-only `.tiinex` state is excluded from canonical manufacture.',
|
|
87
87
|
'',
|
|
88
88
|
`Advanced/internal catalog: ${command} operations`
|
|
89
89
|
];
|
|
@@ -1,13 +1,35 @@
|
|
|
1
|
+
import { prepareNodeSourceFrontier } from '../node/sourceFrontierComparison.js';
|
|
2
|
+
|
|
1
3
|
export async function land(flags, material, readOptionalJson, splitFlag) {
|
|
2
4
|
const repositoriesValue = await readOptionalJson(flags.repositories || flags.repos);
|
|
3
5
|
const selectionsValue = await readOptionalJson(flags.selections);
|
|
6
|
+
const repositories = repositoriesValue.repositories || (Array.isArray(repositoriesValue) ? repositoriesValue : []);
|
|
4
7
|
return {
|
|
5
8
|
input: {
|
|
6
9
|
...material,
|
|
7
|
-
repositories:
|
|
10
|
+
repositories: await attachExactTargetSnapshots(repositories, flags),
|
|
8
11
|
selections: selectionsValue.selections || selectionsValue || repositoriesValue.selections || {},
|
|
9
12
|
workspaceIds: splitFlag(flags.workspaces || flags['workspace-ids'])
|
|
10
13
|
},
|
|
11
14
|
options: {}
|
|
12
15
|
};
|
|
13
16
|
}
|
|
17
|
+
|
|
18
|
+
async function attachExactTargetSnapshots(repositories = [], flags = {}) {
|
|
19
|
+
return Promise.all(repositories.map(async (repository, index) => {
|
|
20
|
+
if (repository?.sourceSnapshot || repository?.snapshot || !String(repository?.root || repository?.path || '').trim()) return repository;
|
|
21
|
+
const root = String(repository.root || repository.path || '').trim();
|
|
22
|
+
const workspaceId = String(repository.workspaceId || repository.id || `landing-target-${index + 1}`);
|
|
23
|
+
const frontier = await prepareNodeSourceFrontier({ kind: 'local-workspace', path: root, workspaceId, label: `workspace-landing-target:${workspaceId}` }, { maxFiles: flags['max-files'] });
|
|
24
|
+
const workspace = (frontier.workspaces || [])[0] || null;
|
|
25
|
+
return {
|
|
26
|
+
...repository,
|
|
27
|
+
sourceSnapshot: workspace?.snapshot || {
|
|
28
|
+
state: 'qualification-error',
|
|
29
|
+
entries: [],
|
|
30
|
+
evidence: {},
|
|
31
|
+
findings: frontier.findings || []
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
@@ -425,7 +425,7 @@ function withCliPhaseTiming(result = {}, timing = {}) {
|
|
|
425
425
|
function parseArgs(argv=[]) {
|
|
426
426
|
const args=[...argv],first=args.shift()||'';
|
|
427
427
|
if(first==='--help'||first==='-h') return {command:'help',flags:{help:true},positionals:[]};
|
|
428
|
-
const command=({orient:'orient-handoff-package',ground:'project-grounding-readiness',receive:'qualify-cold-start',validate:'audit-handoff-package-context',handoff:'manufacture-handoff-package',author:'author',compare:'compare-source-frontiers',reconcile:'prove-source-reconciliation'})[first]||first;
|
|
428
|
+
const command=({orient:'orient-handoff-package',ground:'project-grounding-readiness',receive:'qualify-cold-start',validate:'audit-handoff-package-context',handoff:'manufacture-handoff-package',author:'author',compare:'compare-source-frontiers',reconcile:'prove-source-reconciliation','audit-recovery':'audit-recovery-acceptance'})[first]||first;
|
|
429
429
|
const flags={},positionals=[];
|
|
430
430
|
while(args.length){const token=args.shift();if(!token.startsWith('--')){positionals.push(token);continue;}const key=token.slice(2);flags[key]=!args.length||args[0].startsWith('--')?true:args.shift();}
|
|
431
431
|
return {command,flags,positionals,surfaceCommand:first};
|