@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createPortableSourceFrontier, reconcilePortableSourceFrontiers } from '../comparison/sourceFrontierComparison.js';
|
|
1
2
|
import { inspectRecipientFacingV2Topology } from './recipientV2.inspect.js';
|
|
2
3
|
import { parseWorkspaceEntrypoints, normalizeRepositoryIdentity } from './workspaceSourceIdentity.js';
|
|
3
4
|
|
|
@@ -42,25 +43,25 @@ function planWorkspace({ workspace = {}, provider = null, repositories = [], sel
|
|
|
42
43
|
};
|
|
43
44
|
if (!provider || provider.state !== 'qualified' || provider.mode !== 'archive') {
|
|
44
45
|
if (explicit) findings.push(finding('error', 'portable.workspace-landing.workspace-provider-unqualified', 'Requested Workspace lacks a qualified archive byte provider.', { workspaceId }));
|
|
45
|
-
return freeze({ ...base, state: 'unavailable', source: null, repository: null, reasons: ['workspace-provider-unqualified'] });
|
|
46
|
+
return freeze({ ...base, state: 'unavailable', source: null, repository: null, preflight: unavailablePreflight('workspace-provider-unqualified'), reasons: ['workspace-provider-unqualified'] });
|
|
46
47
|
}
|
|
47
48
|
const target = (provider.entries || []).find((entry) => String(entry.path || '') === base.workspaceArtifactInnerPath);
|
|
48
49
|
if (!target?.data) {
|
|
49
50
|
if (explicit) findings.push(finding('error', 'portable.workspace-landing.workspace-target-unavailable', 'Requested Workspace durable source artifact bytes are unavailable.', { workspaceId }));
|
|
50
|
-
return freeze({ ...base, state: 'unavailable', source: null, repository: null, reasons: ['workspace-target-unavailable'] });
|
|
51
|
+
return freeze({ ...base, state: 'unavailable', source: null, repository: null, preflight: unavailablePreflight('workspace-target-unavailable'), reasons: ['workspace-target-unavailable'] });
|
|
51
52
|
}
|
|
52
53
|
let markdown = '';
|
|
53
54
|
try { markdown = new TextDecoder('utf-8', { fatal: true }).decode(byteView(target.data)); }
|
|
54
55
|
catch {
|
|
55
56
|
findings.push(finding('error', 'portable.workspace-landing.workspace-target-nontext', 'Qualified Workspace target could not be decoded as UTF-8 Markdown.', { workspaceId }));
|
|
56
|
-
return freeze({ ...base, state: 'unavailable', source: null, repository: null, reasons: ['workspace-target-nontext'] });
|
|
57
|
+
return freeze({ ...base, state: 'unavailable', source: null, repository: null, preflight: unavailablePreflight('workspace-target-nontext'), reasons: ['workspace-target-nontext'] });
|
|
57
58
|
}
|
|
58
59
|
const sources = parseWorkspaceEntrypoints(markdown);
|
|
59
60
|
const repoSources = sources.filter((source) => normalizeRepositoryIdentity(source.repository));
|
|
60
61
|
const sourceIdentities = [...new Set(repoSources.map((source) => normalizeRepositoryIdentity(source.repository)))];
|
|
61
62
|
if (sourceIdentities.length !== 1) {
|
|
62
63
|
if (explicit) findings.push(finding('error', sourceIdentities.length ? 'portable.workspace-landing.workspace-repository-ambiguous' : 'portable.workspace-landing.workspace-repository-missing', 'Requested Workspace must expose exactly one explicit repository identity before it can target a local Git repository.', { workspaceId, count: sourceIdentities.length }));
|
|
63
|
-
return freeze({ ...base, state: 'not-targetable', source: repoSources[0] || null, repository: null, reasons: [sourceIdentities.length ? 'workspace-repository-ambiguous' : 'workspace-repository-missing'] });
|
|
64
|
+
return freeze({ ...base, state: 'not-targetable', source: repoSources[0] || null, repository: null, preflight: unavailablePreflight(sourceIdentities.length ? 'workspace-repository-ambiguous' : 'workspace-repository-missing'), reasons: [sourceIdentities.length ? 'workspace-repository-ambiguous' : 'workspace-repository-missing'] });
|
|
64
65
|
}
|
|
65
66
|
const source = repoSources.find((item) => normalizeRepositoryIdentity(item.repository) === sourceIdentities[0]) || repoSources[0];
|
|
66
67
|
const matches = repositories.filter((repo) => repo.repositoryIdentity === sourceIdentities[0]);
|
|
@@ -72,28 +73,112 @@ function planWorkspace({ workspace = {}, provider = null, repositories = [], sel
|
|
|
72
73
|
if (!selected) {
|
|
73
74
|
if (matches.length > 1) findings.push(finding('error', 'portable.workspace-landing.local-repository-ambiguous', 'Multiple local repositories match the same qualified Workspace repository identity; explicit selection is required.', { workspaceId, repository: source.repository, count: matches.length }));
|
|
74
75
|
else if (explicit || selectionId) findings.push(finding('error', 'portable.workspace-landing.local-repository-unmatched', 'Requested Workspace has no matching local Git repository.', { workspaceId, repository: source.repository }));
|
|
75
|
-
return freeze({ ...base, state: matches.length > 1 ? 'ambiguous' : 'unmatched', source, repository: null, candidateRepositoryIds: matches.map((item) => item.id), reasons: [matches.length > 1 ? 'local-repository-ambiguous' : 'local-repository-unmatched'] });
|
|
76
|
+
return freeze({ ...base, state: matches.length > 1 ? 'ambiguous' : 'unmatched', source, repository: null, preflight: unavailablePreflight(matches.length > 1 ? 'local-repository-ambiguous' : 'local-repository-unmatched'), candidateRepositoryIds: matches.map((item) => item.id), reasons: [matches.length > 1 ? 'local-repository-ambiguous' : 'local-repository-unmatched'] });
|
|
76
77
|
}
|
|
77
78
|
const reasons = [];
|
|
78
|
-
if (selected.clean !== true) {
|
|
79
|
-
reasons.push(selected.clean === false ? 'dirty-worktree' : 'clean-state-unresolved');
|
|
80
|
-
findings.push(finding('error', selected.clean === false ? 'portable.workspace-landing.local-repository-dirty' : 'portable.workspace-landing.local-repository-clean-state-unresolved', 'Landing requires a clean target worktree except ignored local material.', { workspaceId, repositoryId: selected.id }));
|
|
81
|
-
}
|
|
82
79
|
const declaredRef = String(source.ref || '').trim();
|
|
83
80
|
const branch = String(selected.branch || '').trim();
|
|
84
81
|
if (declaredRef && branch && declaredRef !== branch) {
|
|
85
82
|
reasons.push('ref-branch-mismatch');
|
|
86
83
|
findings.push(finding('error', 'portable.workspace-landing.ref-branch-mismatch', 'Local repository branch differs from the qualified Workspace source ref safety constraint.', { workspaceId, repositoryId: selected.id, declaredRef, branch }));
|
|
87
84
|
}
|
|
85
|
+
const preflight = projectExactLandingPreflight(workspaceId, provider, selected, findings);
|
|
86
|
+
if (preflight.state !== 'exact-safe') reasons.push(preflight.state === 'reconciliation-required' ? 'reconciliation-required' : 'target-source-snapshot-unresolved');
|
|
88
87
|
return freeze({
|
|
89
88
|
...base,
|
|
90
89
|
state: reasons.length ? 'blocked' : 'ready',
|
|
91
90
|
source,
|
|
92
|
-
repository: freeze({ id: selected.id, root: selected.root, repository: selected.repository, repositoryIdentity: selected.repositoryIdentity, branch, clean: selected.clean }),
|
|
91
|
+
repository: freeze({ id: selected.id, root: selected.root, repository: selected.repository, repositoryIdentity: selected.repositoryIdentity, branch, clean: selected.clean, sourceSnapshotState: String(selected.sourceSnapshot?.state || '') }),
|
|
92
|
+
preflight,
|
|
93
93
|
reasons
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
function projectExactLandingPreflight(workspaceId, provider, selected, findings) {
|
|
98
|
+
const sourceSnapshot = selected.sourceSnapshot;
|
|
99
|
+
if (!sourceSnapshot || String(sourceSnapshot.state || '') !== 'qualified' || !Array.isArray(sourceSnapshot.entries)) {
|
|
100
|
+
findings.push(finding('error', 'portable.workspace-landing.target-source-snapshot-required', 'Landing requires an exact target source-byte snapshot; repository clean state alone cannot prove that replacing the target will preserve newer or divergent work.', { workspaceId, repositoryId: selected.id, clean: selected.clean }));
|
|
101
|
+
return unavailablePreflight('target-source-snapshot-required');
|
|
102
|
+
}
|
|
103
|
+
const incoming = createPortableSourceFrontier({
|
|
104
|
+
id: `landing-incoming:${workspaceId}`,
|
|
105
|
+
source: { kind: 'qualified-handoff-package-workspace', workspaceId },
|
|
106
|
+
workspaces: [{ workspaceId, entries: (provider.entries || []).map(entryIdentity) }]
|
|
107
|
+
});
|
|
108
|
+
const current = createPortableSourceFrontier({
|
|
109
|
+
id: `landing-current:${workspaceId}`,
|
|
110
|
+
source: { kind: 'local-target-source-snapshot', repositoryId: selected.id, root: selected.root },
|
|
111
|
+
workspaces: [{ workspaceId, entries: sourceSnapshot.entries, evidence: sourceSnapshot.evidence || {} }]
|
|
112
|
+
});
|
|
113
|
+
const reconciliation = reconcilePortableSourceFrontiers({ base: incoming, incoming, current });
|
|
114
|
+
const workspace = (reconciliation.workspaces || []).find((item) => String(item.workspaceId || '') === workspaceId) || null;
|
|
115
|
+
if (reconciliation.status !== 'ready' || !workspace || ['qualification-error', 'unavailable', 'locked'].includes(String(workspace.state || ''))) {
|
|
116
|
+
findings.push(finding('error', 'portable.workspace-landing.target-source-snapshot-unqualified', 'Exact target source-byte preflight could not qualify the package/current frontier comparison; landing remains blocked.', { workspaceId, repositoryId: selected.id, reconciliationState: String(workspace?.state || reconciliation.state || '') }));
|
|
117
|
+
return freeze({
|
|
118
|
+
schema: 'tiinex.portable.workspace-landing-preflight.v1',
|
|
119
|
+
state: 'unresolved',
|
|
120
|
+
outcome: 'stop',
|
|
121
|
+
basis: 'accepted-recovery-as-base-and-incoming-vs-exact-current-target',
|
|
122
|
+
counts: freeze({}),
|
|
123
|
+
paths: freeze([]),
|
|
124
|
+
reconciliationState: String(workspace?.state || reconciliation.state || ''),
|
|
125
|
+
boundary: landingPreflightBoundary()
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const paths = (workspace.paths || []).map((item) => freeze({
|
|
129
|
+
path: String(item.path || ''),
|
|
130
|
+
classification: String(item.classification || ''),
|
|
131
|
+
currentChange: String(item.currentChange || ''),
|
|
132
|
+
base: item.base || null,
|
|
133
|
+
incoming: item.incoming || null,
|
|
134
|
+
current: item.current || null
|
|
135
|
+
}));
|
|
136
|
+
if (!paths.length && String(workspace.state || '') === 'exact') {
|
|
137
|
+
return freeze({
|
|
138
|
+
schema: 'tiinex.portable.workspace-landing-preflight.v1',
|
|
139
|
+
state: 'exact-safe',
|
|
140
|
+
outcome: 'land',
|
|
141
|
+
basis: 'accepted-recovery-as-base-and-incoming-vs-exact-current-target',
|
|
142
|
+
counts: workspace.counts || freeze({ currentOnly: 0, conflictCandidate: 0, total: 0 }),
|
|
143
|
+
paths: freeze([]),
|
|
144
|
+
reconciliationState: 'exact',
|
|
145
|
+
boundary: landingPreflightBoundary()
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const counts = workspace.counts || {};
|
|
149
|
+
findings.push(finding('error', 'portable.workspace-landing.reconciliation-required', 'Target source diverges from the accepted Recovery snapshot. Landing must stop before mutation and requires explicit reconciliation or preservation of the reported current-side changes.', {
|
|
150
|
+
workspaceId,
|
|
151
|
+
repositoryId: selected.id,
|
|
152
|
+
currentOnly: Number(counts.currentOnly || 0),
|
|
153
|
+
conflictCandidate: Number(counts.conflictCandidate || 0),
|
|
154
|
+
changedPaths: Number(counts.total || paths.length)
|
|
155
|
+
}));
|
|
156
|
+
return freeze({
|
|
157
|
+
schema: 'tiinex.portable.workspace-landing-preflight.v1',
|
|
158
|
+
state: 'reconciliation-required',
|
|
159
|
+
outcome: 'stop',
|
|
160
|
+
basis: 'accepted-recovery-as-base-and-incoming-vs-exact-current-target',
|
|
161
|
+
counts,
|
|
162
|
+
paths: freeze(paths),
|
|
163
|
+
reconciliationState: String(workspace.state || reconciliation.state || ''),
|
|
164
|
+
boundary: landingPreflightBoundary()
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function unavailablePreflight(reason = '') {
|
|
169
|
+
return freeze({
|
|
170
|
+
schema: 'tiinex.portable.workspace-landing-preflight.v1',
|
|
171
|
+
state: 'unresolved',
|
|
172
|
+
outcome: 'stop',
|
|
173
|
+
reason: String(reason || ''),
|
|
174
|
+
basis: 'exact-target-source-required',
|
|
175
|
+
counts: freeze({}),
|
|
176
|
+
paths: freeze([]),
|
|
177
|
+
reconciliationState: 'unresolved',
|
|
178
|
+
boundary: landingPreflightBoundary()
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
97
182
|
function result(status, inspection, workspaces, findings, input) {
|
|
98
183
|
const affected = workspaces.filter((item) => item.state === 'ready');
|
|
99
184
|
const untouched = workspaces.filter((item) => item.state !== 'ready');
|
|
@@ -109,12 +194,12 @@ function result(status, inspection, workspaces, findings, input) {
|
|
|
109
194
|
required: affected.length > 0,
|
|
110
195
|
repositoryCount: affected.length,
|
|
111
196
|
repositoryRoots: freeze(affected.map((item) => String(item.repository?.root || '')).filter(Boolean)),
|
|
112
|
-
statement: affected.length ? `
|
|
197
|
+
statement: affected.length ? `Exact source preflight passed for ${affected.length} local Git repositor${affected.length === 1 ? 'y' : 'ies'}; replace qualified non-ignored Workspace source, preserve .git and unrelated excluded local material, and do not commit or push.` : '',
|
|
113
198
|
authority: 'explicit-human-host-confirmation-only'
|
|
114
199
|
}),
|
|
115
200
|
operationBoundary: freeze({ sourceMutation: false, remoteWrite: false, commit: false, push: false, acceptance: false }),
|
|
116
201
|
findings: freeze(findings),
|
|
117
|
-
boundary: 'Planning-only projection from one qualified Handoff package plus explicit local Git repository facts. Repository/ref matching
|
|
202
|
+
boundary: 'Planning-only projection from one qualified Handoff package plus explicit local Git repository facts and exact target source-byte snapshots. Repository/ref matching and byte preflight are operational safety constraints, not semantic authority. Clean/dirty Git state alone never authorizes replacement. The plan does not extract, write, commit, push, accept, or complete any Workspace.'
|
|
118
203
|
});
|
|
119
204
|
}
|
|
120
205
|
|
|
@@ -128,10 +213,22 @@ function normalizeRepositories(value = []) {
|
|
|
128
213
|
repository,
|
|
129
214
|
repositoryIdentity: normalizeRepositoryIdentity(repository),
|
|
130
215
|
branch: String(item?.branch || ''),
|
|
131
|
-
clean: item?.clean === true ? true : item?.clean === false ? false : null
|
|
216
|
+
clean: item?.clean === true ? true : item?.clean === false ? false : null,
|
|
217
|
+
sourceSnapshot: normalizeSourceSnapshot(item?.sourceSnapshot || item?.snapshot || null)
|
|
132
218
|
});
|
|
133
219
|
}));
|
|
134
220
|
}
|
|
221
|
+
function normalizeSourceSnapshot(value) {
|
|
222
|
+
if (!value || typeof value !== 'object') return null;
|
|
223
|
+
return freeze({
|
|
224
|
+
state: String(value.state || (Array.isArray(value.entries) ? 'qualified' : '')),
|
|
225
|
+
entries: Array.isArray(value.entries) ? value.entries.map((entry) => entryIdentity(entry)) : [],
|
|
226
|
+
evidence: value.evidence && typeof value.evidence === 'object' ? { ...value.evidence } : {},
|
|
227
|
+
findings: Array.isArray(value.findings) ? value.findings.map((item) => ({ ...item })) : []
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
function entryIdentity(entry = {}) { return freeze({ path: String(entry.path || entry.innerPath || ''), bytes: Number(entry.bytes ?? entry.size ?? 0), sha256: String(entry.sha256 || '').toLowerCase() }); }
|
|
231
|
+
function landingPreflightBoundary() { return 'Read-only Recovery landing safety proof. The accepted carried Workspace is used as both base and incoming so every target deviation is current-side divergence; explicit reconciliation remains separate and no mutation or semantic merge is performed.'; }
|
|
135
232
|
function normalizeSelections(value = {}) {
|
|
136
233
|
const source = value?.selections && typeof value.selections === 'object' ? value.selections : value;
|
|
137
234
|
return Object.fromEntries(Object.entries(source || {}).map(([key, selected]) => [normalizeId(key), String(selected || '')]).filter(([key, selected]) => key && selected));
|
|
@@ -7,6 +7,7 @@ import { projectPortableHandoffCarrierOutputFromPackage } from './handoff/recipi
|
|
|
7
7
|
import { orientColdConsumerFromHandoffPackage } from './handoff/coldConsumerEntrypoint.js';
|
|
8
8
|
import { auditHandoffPackageContextCarriage } from './handoff/contextAudit.js';
|
|
9
9
|
import { projectPortableWorkspaceLandingPlan } from './handoff/workspaceLandingPlan.js';
|
|
10
|
+
import { auditPortableRecoveryAcceptance } from './handoff/recoveryAcceptanceAudit.js';
|
|
10
11
|
import { projectPortableEditorAssistance } from './editor/editor.assistance.js';
|
|
11
12
|
import { projectQualifiedHandoffLeaves } from './handoff/handoffLeafProjection.js';
|
|
12
13
|
import { projectPortableAuthoringParent } from './editor/authoring.parent.js';
|
|
@@ -62,11 +63,18 @@ export function createPortablePackageOperationEntries({ operation, wrapPortableR
|
|
|
62
63
|
}),
|
|
63
64
|
'project-workspace-landing': operation({
|
|
64
65
|
name: 'project-workspace-landing',
|
|
65
|
-
description: 'Project qualified carried Workspaces onto explicit local Git repository facts
|
|
66
|
+
description: 'Project qualified carried Workspaces onto explicit local Git repository facts plus exact target source-byte snapshots, failing closed on any target divergence before extraction or mutation.',
|
|
66
67
|
safety: 'planning-only-read-only',
|
|
67
68
|
inputSchema: 'tiinex.portable.workspace-landing-plan.request.v1',
|
|
68
69
|
handler: (input = {}) => wrapPortableResult('project-workspace-landing', projectPortableWorkspaceLandingPlan(input))
|
|
69
70
|
}),
|
|
71
|
+
'audit-recovery-acceptance': operation({
|
|
72
|
+
name: 'audit-recovery-acceptance',
|
|
73
|
+
description: 'Audit one qualified candidate Recovery carrier against one explicit accepted-basis carrier for complete Workspace re-materialization and unexplained source removals without granting acceptance.',
|
|
74
|
+
safety: 'read-only-mechanical-audit',
|
|
75
|
+
inputSchema: 'tiinex.portable.recovery-acceptance-audit.request.v1',
|
|
76
|
+
handler: (input = {}) => wrapPortableResult('audit-recovery-acceptance', auditPortableRecoveryAcceptance(input))
|
|
77
|
+
}),
|
|
70
78
|
'project-editor-assistance': operation({
|
|
71
79
|
name: 'project-editor-assistance',
|
|
72
80
|
description: 'Project exact shared validator findings, deterministic source locations where provable, and bounded deterministic hygiene repairs for editor hosts.',
|