@tiinex/core 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -6
- package/src/public/index.js +21 -0
- package/src/public/node.js +1 -0
- package/src/release/plan.mjs +18 -5
- package/src/release/run.mjs +7 -5
- package/src/tooling/portable/adapters/cli/cli.command-input.js +3 -0
- package/src/tooling/portable/adapters/cli/cli.common-output.js +23 -0
- package/src/tooling/portable/adapters/cli/cli.handoff-manufacture.js +3 -1
- package/src/tooling/portable/adapters/cli/cli.help.js +18 -2
- package/src/tooling/portable/adapters/cli/cli.material-policy.js +0 -1
- package/src/tooling/portable/adapters/cli/cli.operator-bridge.js +0 -13
- package/src/tooling/portable/adapters/cli/cli.run.js +1 -1
- package/src/tooling/portable/adapters/cli/cli.source-frontier-comparison.js +50 -0
- package/src/tooling/portable/adapters/node/handoff.manufacture.js +14 -0
- package/src/tooling/portable/adapters/node/handoff.manufacture.packageParent.js +80 -16
- package/src/tooling/portable/adapters/node/handoff.manufacture.requirements.js +44 -11
- package/src/tooling/portable/adapters/node/handoff.manufacture.scope.js +96 -1
- package/src/tooling/portable/adapters/node/sourceFrontierComparison.js +194 -0
- package/src/tooling/portable/comparison/sourceFrontierComparison.js +501 -0
- package/src/tooling/portable/grounding/grounding.readiness.js +8 -4
- package/src/tooling/portable/grounding/grounding.readiness.support.js +63 -1
- package/src/tooling/portable/handoff/contextAudit.js +21 -1
- package/src/tooling/portable/handoff/materialClosure.descriptor.js +1 -1
- package/src/tooling/portable/handoff/materialClosure.materials.js +1 -1
- package/src/tooling/portable/handoff/recipientV2.artifacts.js +1 -1
- package/src/tooling/portable/handoff/recipientV2.inspect.helpers.js +6 -1
- package/src/tooling/portable/handoff/recipientV2.packageV1.build.js +93 -19
- package/src/tooling/portable/handoff/recipientV2.packageV1.contract.js +30 -1
- package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.helpers.js +8 -2
- package/src/tooling/portable/handoff/recipientV2.packageV1.inspect.js +122 -10
- package/src/tooling/portable/handoff/recipientV2.packageV1.js +2 -1
- package/src/tooling/portable/handoff/recipientV2.packageV1.secure.js +87 -0
- package/src/tooling/portable/handoff/recipientV2.packageV1.shared.js +9 -1
- package/src/tooling/portable/handoff/recipientV2.topology.js +2 -2
- package/src/tooling/portable/handoff/recipientV2.topology.materials.js +9 -1
- package/src/tooling/portable/handoff/transportEnvelopeV1.js +76 -0
- package/src/tooling/portable/index.js +4 -0
- package/src/tooling/portable/operation.catalog.js +8 -0
- package/src/tooling/portable/operation.catalog.package.js +0 -8
- package/src/transport/secureTransportV1.js +339 -0
- package/src/tooling/portable/handoff/sourceFrontierComparison.js +0 -186
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiinex/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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,
|
|
@@ -133,7 +133,9 @@
|
|
|
133
133
|
"./portable-entry": "./tools/tiinex-portable.mjs",
|
|
134
134
|
"./schema-bootstrap": "./src/tooling/portable/schema/bootstrap/canonical.pack.js",
|
|
135
135
|
"./node/release": "./src/release/run.mjs",
|
|
136
|
-
"./node/release-policy": "./src/release/policy.mjs"
|
|
136
|
+
"./node/release-policy": "./src/release/policy.mjs",
|
|
137
|
+
"./tooling/portable/comparison/sourceFrontierComparison.js": "./src/tooling/portable/comparison/sourceFrontierComparison.js",
|
|
138
|
+
"./tooling/portable/adapters/node/sourceFrontierComparison.js": "./src/tooling/portable/adapters/node/sourceFrontierComparison.js"
|
|
137
139
|
},
|
|
138
140
|
"files": [
|
|
139
141
|
"src",
|
|
@@ -165,12 +167,12 @@
|
|
|
165
167
|
"type": "git",
|
|
166
168
|
"url": "git+https://github.com/Tiinex/core.git"
|
|
167
169
|
},
|
|
168
|
-
"gitHead": "
|
|
170
|
+
"gitHead": "52af987f64464878ba9c497921e21b70215c6971",
|
|
169
171
|
"tiinexRelease": {
|
|
170
172
|
"policy": "tiinex.master-npm-release.v1",
|
|
171
|
-
"sourceCommit": "
|
|
172
|
-
"sourceTree": "
|
|
173
|
+
"sourceCommit": "52af987f64464878ba9c497921e21b70215c6971",
|
|
174
|
+
"sourceTree": "1a8f10b3166397e65f47d43472abb70367374d4c",
|
|
173
175
|
"repository": "Tiinex/core",
|
|
174
|
-
"previousVersion": "0.
|
|
176
|
+
"previousVersion": "0.4.0"
|
|
175
177
|
}
|
|
176
178
|
}
|
package/src/public/index.js
CHANGED
|
@@ -14,3 +14,24 @@ export {
|
|
|
14
14
|
} from './companionResources.js';
|
|
15
15
|
|
|
16
16
|
export { projectSchemaAncestry } from './schemaAncestry.js';
|
|
17
|
+
export {
|
|
18
|
+
SECURE_TRANSPORT_V1_PROFILE,
|
|
19
|
+
SECURE_TRANSPORT_V1_ENVELOPE_CONTRACT,
|
|
20
|
+
qualifySecureTransportV1Envelope,
|
|
21
|
+
sealPasswordWorkspacePayload,
|
|
22
|
+
openPasswordWorkspacePayload,
|
|
23
|
+
replacePasswordWorkspaceRecipients,
|
|
24
|
+
secureTransportV1AuthenticatedMetadata
|
|
25
|
+
} from '../transport/secureTransportV1.js';
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
PORTABLE_SOURCE_FRONTIER_SCHEMA_ID,
|
|
29
|
+
PORTABLE_SOURCE_FRONTIER_COMPARISON_SCHEMA_ID,
|
|
30
|
+
PORTABLE_SOURCE_FRONTIER_SUMMARY_SCHEMA_ID,
|
|
31
|
+
createPortableSourceFrontier,
|
|
32
|
+
createPortableWorkspaceSnapshot,
|
|
33
|
+
comparePortableSourceFrontiers,
|
|
34
|
+
reconcilePortableSourceFrontiers,
|
|
35
|
+
compareOrReconcilePortableSourceFrontiers,
|
|
36
|
+
projectPortableSourceFrontierComparisonSummary
|
|
37
|
+
} from '../tooling/portable/comparison/sourceFrontierComparison.js';
|
package/src/public/node.js
CHANGED
package/src/release/plan.mjs
CHANGED
|
@@ -16,13 +16,26 @@ export function releasePlan({pkg,metadata,sourceCommit,messages=[],previousPacka
|
|
|
16
16
|
if(metadata?.versions?.[decision.targetVersion])throw Error('release.version.collision');
|
|
17
17
|
return {policy:RELEASE_POLICY,action:'publish',name:pkg.name,version:decision.targetVersion,previousVersion:last?.version||null,previousCommit:last?.tiinexRelease?.sourceCommit||last?.gitHead||null,sourceCommit,decision};
|
|
18
18
|
}
|
|
19
|
+
export function publishedArchiveState(metadata,{version,integrity}) {
|
|
20
|
+
const published=metadata?.versions?.[version];
|
|
21
|
+
if(!published)return {status:'absent',published:null};
|
|
22
|
+
if(published.dist?.integrity!==integrity)return {status:'collision',published};
|
|
23
|
+
return {status:'exact',published};
|
|
24
|
+
}
|
|
19
25
|
export function assertPublishContext(env,repository) {
|
|
20
26
|
if(env.GITHUB_ACTIONS!=='true'||!['push','workflow_dispatch'].includes(env.GITHUB_EVENT_NAME)||env.GITHUB_REF!=='refs/heads/master'||env.GITHUB_REPOSITORY!==repository)throw Error('release.master-only: publishing requires this repository on refs/heads/master');
|
|
21
27
|
if(env.TIINEX_ENABLE_NPM_PUBLISH!=='true')throw Error('release.not-enabled');
|
|
22
28
|
}
|
|
23
|
-
export async function readRegistry(name,{fetcher=globalThis.fetch}={}) {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
export async function readRegistry(name,{fetcher=globalThis.fetch,attempts=1,retryDelayMs=250,sleeper=(ms)=>new Promise(resolve=>setTimeout(resolve,ms))}={}) {
|
|
30
|
+
const count=Math.max(1,Number.isInteger(attempts)?attempts:1);
|
|
31
|
+
for(let attempt=1;attempt<=count;attempt+=1){
|
|
32
|
+
const response=await fetcher(`https://registry.npmjs.org/${encodeURIComponent(name)}`,{headers:{Accept:'application/json','Cache-Control':'no-cache'},signal:AbortSignal.timeout(30000)});
|
|
33
|
+
if(response.status===404){
|
|
34
|
+
if(attempt<count){await sleeper(Math.max(0,retryDelayMs)*attempt);continue;}
|
|
35
|
+
return {name,versions:{}};
|
|
36
|
+
}
|
|
37
|
+
if(!response.ok)throw Error(`release.registry.failure:${response.status}`);
|
|
38
|
+
const data=await response.json();if(data.name!==name||!data.versions||typeof data.versions!=='object')throw Error('release.registry.invalid-response');return data;
|
|
39
|
+
}
|
|
40
|
+
return {name,versions:{}};
|
|
28
41
|
}
|
package/src/release/run.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import {readFile,writeFile,mkdir,rm,cp,realpath,stat} from 'node:fs/promises';
|
|
|
2
2
|
import {spawnSync} from 'node:child_process';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import {createHash} from 'node:crypto';
|
|
5
|
-
import {assertPublishContext,readRegistry,releasePlan,compareVersion} from './plan.mjs';
|
|
5
|
+
import {assertPublishContext,readRegistry,releasePlan,compareVersion,publishedArchiveState} from './plan.mjs';
|
|
6
6
|
import {parseSemver} from './policy.mjs';
|
|
7
7
|
const npm=process.platform==='win32'?'npm.cmd':'npm';
|
|
8
8
|
function run(command,args,cwd,{allowFailure=false}={}) {const r=spawnSync(command,args,{cwd,encoding:'utf8',timeout:180000,maxBuffer:32*1024*1024,shell:process.platform==='win32'&&command===npm});if(r.error||(!allowFailure&&r.status!==0))throw Error(`${command} ${args[0]} failed: ${r.error?.message||r.stderr||r.stdout}`);return r;}
|
|
@@ -16,6 +16,7 @@ export async function runRelease({cwd=process.cwd(),argv=process.argv.slice(2),e
|
|
|
16
16
|
const pkg=JSON.parse(await readFile(path.join(cwd,'package.json'),'utf8'));
|
|
17
17
|
const policy=JSON.parse(await readFile(path.join(cwd,'.github/release-policy.json'),'utf8'));
|
|
18
18
|
const repository=policy.repository;
|
|
19
|
+
const registryReadOptions=command==='bootstrap'?{}:env.GITHUB_ACTIONS==='true'?{attempts:6,retryDelayMs:500}:{};
|
|
19
20
|
if(pkg.repository?.url!==`git+https://github.com/${repository}.git`||!repository.startsWith('Tiinex/'))throw Error('release.repository.mismatch');
|
|
20
21
|
const git=(args,opts)=>run('git',args,cwd,opts);
|
|
21
22
|
const head=git(['rev-parse','HEAD']).stdout.trim();
|
|
@@ -26,18 +27,19 @@ export async function runRelease({cwd=process.cwd(),argv=process.argv.slice(2),e
|
|
|
26
27
|
if(p.action==='skip'){console.log(JSON.stringify(p));return p;}
|
|
27
28
|
const file=path.resolve(cwd,p.file);if(path.dirname(file)!==path.join(cwd,'.release'))throw Error('release.archive-path');
|
|
28
29
|
const bytes=await readFile(file);if(createHash('sha512').update(bytes).digest('base64')!==p.integrity.slice(7))throw Error('release.archive-tampered');
|
|
29
|
-
const meta=await readRegistry(pkg.name);const
|
|
30
|
-
if(
|
|
30
|
+
const meta=await readRegistry(pkg.name,{...registryReadOptions,attempts:8});const existingState=publishedArchiveState(meta,{version:p.version,integrity:p.integrity});
|
|
31
|
+
if(existingState.status==='collision')throw Error('release.version.collision');
|
|
32
|
+
if(existingState.status==='exact'){console.log('Already published exact archive');return p;}
|
|
31
33
|
const latest=Object.values(meta.versions).filter(v=>!parseSemver(v.version).prerelease).sort((a,b)=>compareVersion(a.version,b.version)).at(-1);
|
|
32
34
|
if(latest&&compareVersion(latest.version,p.version)>=0)throw Error('release.newer-version-already-published');
|
|
33
35
|
const result=run(npm,['publish',file,'--ignore-scripts','--access','public','--tag','latest','--provenance'],cwd,{allowFailure:true});
|
|
34
|
-
if(result.status!==0){const check=await readRegistry(pkg.name);
|
|
36
|
+
if(result.status!==0){const check=await readRegistry(pkg.name,{...registryReadOptions,attempts:10,retryDelayMs:750});const failedState=publishedArchiveState(check,{version:p.version,integrity:p.integrity});if(failedState.status==='collision')throw Error('release.version.collision-after-publish');if(failedState.status!=='exact')throw Error('release.publish.failed: '+result.stderr);}
|
|
35
37
|
console.log(JSON.stringify({...p,published:true}));return p;
|
|
36
38
|
}
|
|
37
39
|
if(command==='prepare') {assertPublishContext(env,repository);if(env.GITHUB_SHA!==head)throw Error('release.checkout-is-not-event-commit');}
|
|
38
40
|
if(command==='bootstrap' && git(['branch','--show-current']).stdout.trim()!=='master')throw Error('release.bootstrap.master-only');
|
|
39
41
|
if(git(['status','--porcelain','--untracked-files=no']).stdout.trim())throw Error('release.dirty-source');
|
|
40
|
-
const metadata=await readRegistry(pkg.name);
|
|
42
|
+
const metadata=await readRegistry(pkg.name,registryReadOptions);
|
|
41
43
|
if(command==='bootstrap'&&Object.keys(metadata.versions).length)throw Error('Package exists: configure OIDC and use the master workflow, not bootstrap.');
|
|
42
44
|
if(command==='bootstrap') { const check=pkg.scripts?.validate?'validate':pkg.scripts?.check?'check':'test'; run(npm,['run',check],cwd); }
|
|
43
45
|
const previous=Object.values(metadata.versions).filter(v=>!parseSemver(v.version).prerelease).sort((a,b)=>compareVersion(a.version,b.version)).at(-1);
|
|
@@ -10,6 +10,7 @@ import { prepareQualifyColdStartCommandInput } from './cli.cold-start-input.js';
|
|
|
10
10
|
import { reductionPreflightCliInput } from './cli.reduction-input.js';
|
|
11
11
|
import {land} from './cli.land.js';
|
|
12
12
|
import { OPERATIONS_WITHOUT_EXPLICIT_MATERIAL } from './cli.material-policy.js';
|
|
13
|
+
import { prepareSourceFrontierComparisonCliInput } from './cli.source-frontier-comparison.js';
|
|
13
14
|
|
|
14
15
|
export async function commandInput(parsed, runtime = {}) {
|
|
15
16
|
const flags = parsed.flags;
|
|
@@ -138,6 +139,8 @@ export async function commandInput(parsed, runtime = {}) {
|
|
|
138
139
|
return { input: { plan: plan.result || plan, receipt: receipt.result || receipt, priorAcceptance: prior.result || prior }, options: {} };
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
if (parsed.command === 'compare-source-frontiers') return prepareSourceFrontierComparisonCliInput(parsed, flags);
|
|
143
|
+
|
|
141
144
|
if (parsed.command === 'manufacture-handoff-package') {
|
|
142
145
|
return prepareHandoffManufactureCliCommand(parsed, runtime);
|
|
143
146
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { projectPortableSourceFrontierComparisonSummary } from '../../comparison/sourceFrontierComparison.js';
|
|
2
|
+
|
|
1
3
|
const COMMON_DEFAULT_PROJECTION = 'common-default';
|
|
2
4
|
|
|
3
5
|
export function projectCommonCliDefaultOutput(result = {}, parsed = {}) {
|
|
@@ -5,9 +7,30 @@ export function projectCommonCliDefaultOutput(result = {}, parsed = {}) {
|
|
|
5
7
|
if (parsed?.command === 'orient-handoff-package') return projectOrientDefault(result, parsed);
|
|
6
8
|
if (parsed?.command === 'project-grounding-readiness') return projectGroundDefault(result, parsed);
|
|
7
9
|
if (parsed?.command === 'manufacture-handoff-package' && parsed?.surfaceCommand === 'handoff') return projectHandoffDefault(result, parsed);
|
|
10
|
+
if (parsed?.command === 'compare-source-frontiers') return projectCompareDefault(result, parsed);
|
|
8
11
|
return result;
|
|
9
12
|
}
|
|
10
13
|
|
|
14
|
+
function projectCompareDefault(result = {}, parsed = {}) {
|
|
15
|
+
const summary = projectPortableSourceFrontierComparisonSummary({ ...result, schema: result.resultSchema || result.schema || '' }, { maxPaths: parsed?.flags?.['max-paths'] || 20 });
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
schema: result.schema,
|
|
18
|
+
operation: result.operation || 'compare-source-frontiers',
|
|
19
|
+
resultSchema: result.resultSchema,
|
|
20
|
+
projection: COMMON_DEFAULT_PROJECTION,
|
|
21
|
+
status: summary.status,
|
|
22
|
+
state: summary.state,
|
|
23
|
+
mode: summary.mode,
|
|
24
|
+
inputs: summary.inputs,
|
|
25
|
+
workspaces: summary.workspaces,
|
|
26
|
+
counts: summary.counts,
|
|
27
|
+
findingSummary: summary.findingSummary,
|
|
28
|
+
actionableFindings: summary.actionableFindings,
|
|
29
|
+
detail: Object.freeze({ fullReceipt: Object.freeze({ command: String(parsed.surfaceCommand || 'compare'), flag: '--full' }) }),
|
|
30
|
+
boundary: summary.boundary
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
11
34
|
function projectOrientDefault(result = {}, parsed = {}) {
|
|
12
35
|
const projection = result?.entrypoint?.projection || {};
|
|
13
36
|
const routes = (result.routes || projection.routes || []).map(projectOrientRoute);
|
|
@@ -28,6 +28,7 @@ export async function prepareHandoffManufactureCliCommand(parsed = {}, runtime =
|
|
|
28
28
|
if (!flags.route && handoffPath) flags.route = handoffPath;
|
|
29
29
|
if (!flags.output && !flags['output-dir'] && parsed.surfaceCommand === 'handoff' && continuationState.returnOutputDir) flags['output-dir'] = continuationState.returnOutputDir;
|
|
30
30
|
const materialBindings = await readOptionalJson(flags['material-bindings'] || flags.materials);
|
|
31
|
+
const packageParentWorkspaceIds = splitFlag(flags['package-parent-workspaces']);
|
|
31
32
|
const packageParentWorkspaceAliases = await readOptionalJson(flags['package-parent-workspace-aliases']);
|
|
32
33
|
const operatorCarrierProfile = await readOptionalJson(flags['carrier-profile']);
|
|
33
34
|
const expectedToolingBootstrap = await readOptionalJson(flags['tooling-bootstrap-manifest']);
|
|
@@ -126,6 +127,7 @@ export async function prepareHandoffManufactureCliCommand(parsed = {}, runtime =
|
|
|
126
127
|
packageParentBundle,
|
|
127
128
|
packageParentPath: parentPackagePath ? path.resolve(parentPackagePath) : '',
|
|
128
129
|
packageParentSha256,
|
|
130
|
+
packageParentWorkspaceIds,
|
|
129
131
|
packageParentWorkspaceAliases
|
|
130
132
|
}, runtime);
|
|
131
133
|
return {
|
|
@@ -193,7 +195,7 @@ function projectWorkspaceCarrierHumanOutput(result = {}) {
|
|
|
193
195
|
const projection = result.carrierProjection || {};
|
|
194
196
|
const ready = result.status === 'ready' && projection.status === 'ready' && projection.mode === 'workspace' && (projection.routes || []).length === 0;
|
|
195
197
|
const dimension = String(projection.lineage?.dimension || '001');
|
|
196
|
-
const filename = `tiinex
|
|
198
|
+
const filename = `tiinex-${dimension}.handoff-package.zip`;
|
|
197
199
|
return Object.freeze({
|
|
198
200
|
schema: 'tiinex.portable.handoff-human-output.v1',
|
|
199
201
|
status: ready ? 'ready' : 'blocked',
|
|
@@ -12,6 +12,7 @@ export function portableCliHelpText(commandPrefix = '', surfaceCommand = '') {
|
|
|
12
12
|
`${command} ground <handoff-package.zip> --route <Continue-from> --holder-role <recipient-role> --continue <workspace-dir>`,
|
|
13
13
|
`${command} author <workspace-dir> --schema <schema-id> (--path <workspace-relative-artifact> | --directory <workspace-relative-directory>) --body <body.md> [--parent <workspace-relative-or-qualified-parent>] [--parent-source <local-parent-file>] [--title <title>] [--summary <summary>] [--why <why>]`,
|
|
14
14
|
`${command} handoff <workspace-dir>`,
|
|
15
|
+
`${command} compare --left-kind <kind> --left <path> --right-kind <kind> --right <path> [side selectors]`,
|
|
15
16
|
'',
|
|
16
17
|
`Handoff aliases: ${command} orient <carrier.zip>; ${command} validate <carrier.zip>`,
|
|
17
18
|
`Advanced/internal catalog: ${command} operations`,
|
|
@@ -20,7 +21,7 @@ export function portableCliHelpText(commandPrefix = '', surfaceCommand = '') {
|
|
|
20
21
|
'- `orient`, `ground`, and public `handoff` use compact decision-first default projections; add `--full` on the same command for the complete qualified receipt.',
|
|
21
22
|
'- `ground` is read-only; append `--continue <workspace-dir>` only after `grounded-to-act` to materialize the selected carried Workspace and runtime-only `.tiinex/continuation.json`.',
|
|
22
23
|
'- `author` uses continuation state to infer ordinary Parent continuity, seal c14n-v2 integrity, audit, and stage; invalid artifacts are not retained.',
|
|
23
|
-
'- `handoff` uses continuation state plus the latest qualified authored Handoff to manufacture the canonical return carrier and excludes runtime-only `.tiinex` state.
|
|
24
|
+
'- `handoff` uses continuation state plus the latest qualified authored Handoff to manufacture the canonical return carrier and excludes runtime-only `.tiinex` state. A received `--package-parent` continues carrier lineage only; it never selects parent Workspace source by itself. Advanced manufacture may opt into exact parent snapshots with `--package-parent-workspaces <id,...|all>` and may bind an explicitly selected renamed predecessor with `--package-parent-workspace-aliases`; aliases never infer source identity. Normal operator completion is one Handoff package plus the exact routing text; markdown-capable hosts render that routing in a fenced code block, and do not emit loose Evidence/Handoff markdown as extra transport payloads.',
|
|
24
25
|
'- Remote reads/writes remain explicit host concerns. Tooling operation safety does not create or revoke semantic Task/Handoff authority.',
|
|
25
26
|
'',
|
|
26
27
|
'Use `<common-command> --help` for focused common-path usage. Use `operations` deliberately for the advanced/internal operation catalog.'
|
|
@@ -28,6 +29,21 @@ export function portableCliHelpText(commandPrefix = '', surfaceCommand = '') {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
function commonCommandHelp(command, surfaceCommand) {
|
|
32
|
+
if (surfaceCommand === 'compare' || surfaceCommand === 'compare-source-frontiers') return [
|
|
33
|
+
'Tiinex portable tooling — compare source frontiers',
|
|
34
|
+
'',
|
|
35
|
+
'Two-way:',
|
|
36
|
+
`${command} compare --left-kind <local-workspace|local-frontier|handoff-package> --left <path> --right-kind <kind> --right <path> [--left-id <workspace-id>] [--right-id <workspace-id>] [--left-select <id,...>] [--right-select <id,...>]`,
|
|
37
|
+
'For local-frontier inputs use `--left-roots <id=path,...>` / `--right-roots <id=path,...>` instead of the side path.',
|
|
38
|
+
'',
|
|
39
|
+
'Three-way child-return reconciliation:',
|
|
40
|
+
`${command} compare --base-kind <kind> --base <path> --incoming-kind <kind> --incoming <path> --current-kind <kind> --current <path> [side ids/selectors/roots]`,
|
|
41
|
+
'',
|
|
42
|
+
'Input kind is mandatory and is never guessed from a path. Local source uses the exact deterministic Workspace enumeration contract shared with Handoff manufacture. Handoff packages are qualified by this current Tooling runtime; package bootstrap code is not executed. Password-sealed Workspaces remain `locked` unless a caller uses the public Node API with an already-authorized opened Workspace provider.',
|
|
43
|
+
'Default output is a compact path-bounded human/LLM projection; add `--full` for the complete machine receipt. Comparison is read-only source-byte evidence only: no merge, semantic diff, staging, commit, push, remote acquisition, authority, or acceptance inference.',
|
|
44
|
+
'',
|
|
45
|
+
`Advanced/internal catalog: ${command} operations`
|
|
46
|
+
];
|
|
31
47
|
if (surfaceCommand === 'ground') return [
|
|
32
48
|
'Tiinex portable tooling — ground',
|
|
33
49
|
'',
|
|
@@ -54,7 +70,7 @@ function commonCommandHelp(command, surfaceCommand) {
|
|
|
54
70
|
'',
|
|
55
71
|
`${command} handoff <workspace-dir>`,
|
|
56
72
|
'',
|
|
57
|
-
'Infers the latest qualified authored Handoff, selected Workspace identity/target, received package parent
|
|
73
|
+
'Infers the latest qualified authored Handoff, selected Workspace identity/target, received package parent as carrier-lineage evidence, canonical projected filename, and return output directory. 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>`. 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.',
|
|
58
74
|
'',
|
|
59
75
|
`Advanced/internal catalog: ${command} operations`
|
|
60
76
|
];
|
|
@@ -2,6 +2,5 @@ export const OPERATIONS_WITHOUT_EXPLICIT_MATERIAL = new Set([
|
|
|
2
2
|
'prepare-task','prepare-materialization','create-local-artifact-set','create-local-draft','plan-host-action','accept-host-receipt',
|
|
3
3
|
'describe-checkpoint-gate','qualify-checkpoint','describe-schema-chain','schema-guide','plan-artifact','list-material-providers',
|
|
4
4
|
'resolve-schema-material','resolve-schema-chain-material','materialize-durable-findings','build-runtime-package','roundtrip-runtime-package',
|
|
5
|
-
'compare-source-frontiers',
|
|
6
5
|
'describe-cold-start-ingress','project-cold-start-host','qualify-cold-start','ground-cold-consumer'
|
|
7
6
|
]);
|
|
@@ -3,19 +3,6 @@ export async function prepareOperatorBridgeCliInput(command = '', material = {},
|
|
|
3
3
|
const repositories = await readOptionalJson(flags.repositories);
|
|
4
4
|
return { input: { ...material, repositories: repositories.repositories || repositories }, options: {} };
|
|
5
5
|
}
|
|
6
|
-
if (command === 'compare-source-frontiers') {
|
|
7
|
-
return {
|
|
8
|
-
input: {
|
|
9
|
-
leftKind: flags['left-kind'] || flags.leftKind || '',
|
|
10
|
-
left: flags.left || '',
|
|
11
|
-
leftId: flags['left-id'] || flags.leftId || '',
|
|
12
|
-
rightKind: flags['right-kind'] || flags.rightKind || '',
|
|
13
|
-
right: flags.right || '',
|
|
14
|
-
rightSelect: flags['right-select'] || flags.rightSelect || ''
|
|
15
|
-
},
|
|
16
|
-
options: {}
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
6
|
if (command === 'project-handoff-authoring-plan') return { input: { ...material, parentPath: flags.parent || flags['parent-path'] || '', title: flags.title || '' }, options: {} };
|
|
20
7
|
if (command === 'project-handoff-endpoints') return { input: { ...material, workspaceId: flags['workspace-id'] || flags.workspace || 'workspace' }, options: {} };
|
|
21
8
|
if (command === 'project-operator-context') {
|
|
@@ -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'})[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'})[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};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { prepareNodeSourceFrontierComparisonInput } from '../node/sourceFrontierComparison.js';
|
|
2
|
+
|
|
3
|
+
export async function prepareSourceFrontierComparisonCliInput(parsed = {}, flags = {}) {
|
|
4
|
+
const threeWay = hasAny(flags, ['base', 'base-kind', 'incoming', 'incoming-kind', 'current', 'current-kind']);
|
|
5
|
+
const descriptorOptions = {
|
|
6
|
+
maxFiles: flags['max-files'],
|
|
7
|
+
maxCarrierFiles: flags['max-carrier-files'],
|
|
8
|
+
maxTextBytes: flags['max-text-bytes']
|
|
9
|
+
};
|
|
10
|
+
if (threeWay) {
|
|
11
|
+
const request = {
|
|
12
|
+
base: descriptorFromFlags('base', flags),
|
|
13
|
+
incoming: descriptorFromFlags('incoming', flags),
|
|
14
|
+
current: descriptorFromFlags('current', flags)
|
|
15
|
+
};
|
|
16
|
+
return { input: await prepareNodeSourceFrontierComparisonInput(request, descriptorOptions), options: {} };
|
|
17
|
+
}
|
|
18
|
+
const request = {
|
|
19
|
+
left: descriptorFromFlags('left', flags),
|
|
20
|
+
right: descriptorFromFlags('right', flags)
|
|
21
|
+
};
|
|
22
|
+
return { input: await prepareNodeSourceFrontierComparisonInput(request, descriptorOptions), options: {} };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function descriptorFromFlags(prefix, flags) {
|
|
26
|
+
const kind = String(flags[`${prefix}-kind`] || '').trim();
|
|
27
|
+
const root = String(flags[prefix] || '').trim();
|
|
28
|
+
const workspaceId = String(flags[`${prefix}-id`] || flags[`${prefix}-workspace-id`] || '').trim();
|
|
29
|
+
const select = splitFlag(flags[`${prefix}-select`]);
|
|
30
|
+
const roots = parseRootBindings(flags[`${prefix}-roots`]);
|
|
31
|
+
return Object.freeze({
|
|
32
|
+
kind,
|
|
33
|
+
...(root ? { path: root } : {}),
|
|
34
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
35
|
+
...(select.length ? { workspaceIds: select } : {}),
|
|
36
|
+
...(roots.length ? { workspaces: roots } : {}),
|
|
37
|
+
...(flags[`${prefix}-label`] ? { label: String(flags[`${prefix}-label`]) } : {})
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseRootBindings(value) {
|
|
42
|
+
const items = splitFlag(value);
|
|
43
|
+
return items.map((item) => {
|
|
44
|
+
const eq = item.indexOf('=');
|
|
45
|
+
if (eq <= 0 || eq === item.length - 1) return Object.freeze({ workspaceId: '', root: item });
|
|
46
|
+
return Object.freeze({ workspaceId: item.slice(0, eq).trim(), root: item.slice(eq + 1).trim() });
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function splitFlag(value) { return !value || value === true ? [] : String(value).split(',').map((item) => item.trim()).filter(Boolean); }
|
|
50
|
+
function hasAny(flags, keys) { return keys.some((key) => Object.prototype.hasOwnProperty.call(flags, key)); }
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from './handoff.manufacture.requirements.js';
|
|
16
16
|
import {
|
|
17
17
|
expandBoundedParentBoundaryClosure,
|
|
18
|
+
expandRouteParentBoundaryClosure,
|
|
18
19
|
normalizeWorkspaceScopes,
|
|
19
20
|
normalizeWorkspaceTargetBindings,
|
|
20
21
|
projectBoundedWorkspaceMaterialization
|
|
@@ -68,6 +69,7 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
|
|
|
68
69
|
currentWorkspaceIds: [...seenWorkspaceIds],
|
|
69
70
|
parentPackagePath: input.packageParentPath || '',
|
|
70
71
|
parentPackageSha256: input.packageParentSha256 || '',
|
|
72
|
+
workspaceIds: input.packageParentWorkspaceIds || input.reusePackageParentWorkspaceIds || [],
|
|
71
73
|
workspaceAliases: input.packageParentWorkspaceAliases || input.workspaceAliases || {}
|
|
72
74
|
});
|
|
73
75
|
const additionalEnumerationsPromise = Promise.all(additionalWorkspaceInputs.map(async ({ descriptor, id, root, requestedTitle }) => {
|
|
@@ -114,6 +116,11 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
|
|
|
114
116
|
workspaceEnumerations.push(Object.freeze({ id, root: '', evidence: inheritedEnumeration.evidence, provider: 'qualified-package-parent-workspace' }));
|
|
115
117
|
workspaceRuntimeById.set(id, Object.freeze({ id, root: '', enumeration: inheritedEnumeration, provider: 'qualified-package-parent-workspace' }));
|
|
116
118
|
}
|
|
119
|
+
for (const provided of packageParentReuse.providers || []) {
|
|
120
|
+
const id = safeWorkspaceToken(provided.id || provided.enumeration?.materialization?.id || '');
|
|
121
|
+
if (!id || workspaceRuntimeById.has(id)) continue;
|
|
122
|
+
workspaceRuntimeById.set(id, Object.freeze({ id, root: '', enumeration: provided.enumeration, provider: 'qualified-package-parent-workspace-material-provider' }));
|
|
123
|
+
}
|
|
117
124
|
const transportRoutes = Object.freeze([...(input.transportRoutes || input.handoffRoutes || [])].map((route) => normalizeTransportRoute(route, workspaceId)).filter(Boolean));
|
|
118
125
|
const workspaceTargets = mergeWorkspaceTargetBindings(normalizeWorkspaceTargetBindings({
|
|
119
126
|
primaryWorkspaceId: workspaceId,
|
|
@@ -137,6 +144,9 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
|
|
|
137
144
|
const dependencyClosure = await expandPointerDependencyClosure({ requirements, materials, workspaceRuntimeById, bindings: input.materialBindings || {} });
|
|
138
145
|
requirements = dependencyClosure.requirements;
|
|
139
146
|
materials = dependencyClosure.materials;
|
|
147
|
+
const routeParentBoundaryClosure = expandRouteParentBoundaryClosure({ requirements, materials, workspaceMaterializations, workspaceRuntimeById, routeSpecs });
|
|
148
|
+
requirements = routeParentBoundaryClosure.requirements;
|
|
149
|
+
materials = routeParentBoundaryClosure.materials;
|
|
140
150
|
const parentBoundaryClosure = expandBoundedParentBoundaryClosure({ requirements, materials, workspaceMaterializations, workspaceRuntimeById });
|
|
141
151
|
requirements = parentBoundaryClosure.requirements;
|
|
142
152
|
materials = parentBoundaryClosure.materials;
|
|
@@ -167,7 +177,11 @@ export async function prepareNodeHandoffManufacturingInput(input = {}, options =
|
|
|
167
177
|
toolingBootstrap: toolingBootstrap.summary,
|
|
168
178
|
packageParentWorkspaceReuse: Object.freeze({
|
|
169
179
|
state: String(packageParentReuse.state || ''),
|
|
180
|
+
providerState: String(packageParentReuse.providerState || ''),
|
|
170
181
|
inspectionStatus: String(packageParentReuse.inspectionStatus || ''),
|
|
182
|
+
selectionMode: String(packageParentReuse.selectionMode || ''),
|
|
183
|
+
requestedWorkspaceIds: Object.freeze([...(packageParentReuse.requestedWorkspaceIds || [])].map(String)),
|
|
184
|
+
providerWorkspaceIds: Object.freeze([...(packageParentReuse.providerWorkspaceIds || [])].map(String)),
|
|
171
185
|
inheritedWorkspaceIds: Object.freeze((packageParentReuse.inherited || []).map((item) => String(item.id || ''))),
|
|
172
186
|
workspaceAliases: Object.freeze([...(packageParentReuse.workspaceAliases || [])].map((item) => Object.freeze({ ...item }))),
|
|
173
187
|
boundary: String(packageParentReuse.boundary || '')
|
|
@@ -4,48 +4,95 @@ import { parseHandoffPackageV1, RECIPIENT_V2_PACKAGE_V1_ROOT_PATH } from '../../
|
|
|
4
4
|
|
|
5
5
|
export function preparePackageParentWorkspaceReuse(input = {}) {
|
|
6
6
|
const bundle = input.bundle || null;
|
|
7
|
-
if (!bundle?.files?.length) return emptyReuse('unavailable');
|
|
8
7
|
const currentIds = new Set([...(input.currentWorkspaceIds || [])].map(normalizeId).filter(Boolean));
|
|
9
8
|
const workspaceAliases = normalizePackageParentWorkspaceAliases(input.workspaceAliases || input.packageParentWorkspaceAliases || {});
|
|
10
9
|
for (const alias of workspaceAliases.values()) if (!currentIds.has(alias)) throw new Error(`portable.handoff-manufacture.package-parent.workspace-alias.target-unresolved:${alias}`);
|
|
10
|
+
const selection = normalizePackageParentWorkspaceSelection(input.workspaceIds || input.packageParentWorkspaceIds || input.reuseWorkspaceIds || []);
|
|
11
|
+
if (!bundle?.files?.length) return emptyReuse('unavailable', { selection, workspaceAliases });
|
|
11
12
|
const inspection = inspectRecipientFacingV2Topology(bundle);
|
|
12
13
|
const declared = declaredPackageWorkspaceBindings(bundle, inspection);
|
|
13
|
-
if (!declared.length)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
if (!declared.length) {
|
|
15
|
+
if (selection.mode !== 'none') throw new Error('portable.handoff-manufacture.package-parent.workspace-selection.parent-surface-unresolved');
|
|
16
|
+
return emptyReuse('not-requested', { selection, workspaceAliases, inspectionStatus: inspection.status, providerState: 'unsupported-parent-surface' });
|
|
17
|
+
}
|
|
18
|
+
if (inspection.status !== 'valid') {
|
|
19
|
+
if (selection.mode !== 'none') throw new Error('portable.handoff-manufacture.package-parent.workspace-provider.invalid');
|
|
20
|
+
return emptyReuse('not-requested', { selection, workspaceAliases, inspectionStatus: inspection.status, providerState: 'invalid' });
|
|
21
|
+
}
|
|
22
|
+
const selected = selectDeclaredPackageParentWorkspaceBindings(declared, selection);
|
|
23
|
+
const missing = selected.filter((item) => !packageParentWorkspaceSupersededByCurrent(item.workspaceId, currentIds, workspaceAliases));
|
|
17
24
|
|
|
18
25
|
const providerById = new Map((inspection.workspaceByteProvider?.workspaces || []).map((item) => [normalizeId(item.id), item]));
|
|
19
26
|
const inspectedById = new Map((inspection.workspaces || []).map((item) => [normalizeId(item.workspaceId), item]));
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
for (const binding of
|
|
27
|
+
const providers = [];
|
|
28
|
+
const providerTargetById = new Map();
|
|
29
|
+
for (const binding of declared) {
|
|
23
30
|
const id = normalizeId(binding.workspaceId);
|
|
31
|
+
if (packageParentWorkspaceSupersededByCurrent(id, currentIds, workspaceAliases)) continue;
|
|
24
32
|
const provider = providerById.get(id);
|
|
25
33
|
const inspected = inspectedById.get(id);
|
|
26
34
|
if (!provider || provider.state !== 'qualified' || provider.mode !== 'archive' || provider.materialization?.materialization !== 'complete' || inspected?.coverage !== 'complete') {
|
|
27
|
-
throw new Error(`portable.handoff-manufacture.package-parent.workspace-provider.unqualified:${id}`);
|
|
35
|
+
if (selection.mode === 'all' || selection.ids.includes(id)) throw new Error(`portable.handoff-manufacture.package-parent.workspace-provider.unqualified:${id}`);
|
|
36
|
+
continue;
|
|
28
37
|
}
|
|
29
38
|
const targetPath = String(inspected.sourceWorkspaceTargetInnerPath || binding.workspaceArtifactInnerPath || '').trim();
|
|
30
|
-
if (!targetPath)
|
|
31
|
-
|
|
39
|
+
if (!targetPath) {
|
|
40
|
+
if (selection.mode === 'all' || selection.ids.includes(id)) throw new Error(`portable.handoff-manufacture.package-parent.workspace-target.unresolved:${id}`);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
providers.push(buildInheritedEnumeration(provider, {
|
|
32
44
|
parentPackagePath: input.parentPackagePath || '',
|
|
33
45
|
parentPackageSha256: input.parentPackageSha256 || '',
|
|
34
46
|
archivePackagePath: inspected.workspaceArchivePath || binding.snapshotPath || ''
|
|
35
47
|
}));
|
|
36
|
-
|
|
48
|
+
providerTargetById.set(id, targetPath);
|
|
37
49
|
}
|
|
50
|
+
const providerEnumerationById = new Map(providers.map((item) => [normalizeId(item.id), item]));
|
|
51
|
+
const inherited = [];
|
|
52
|
+
const workspaceTargets = [];
|
|
53
|
+
for (const binding of missing) {
|
|
54
|
+
const id = normalizeId(binding.workspaceId);
|
|
55
|
+
const inheritedProvider = providerEnumerationById.get(id);
|
|
56
|
+
if (!inheritedProvider) throw new Error(`portable.handoff-manufacture.package-parent.workspace-provider.unqualified:${id}`);
|
|
57
|
+
inherited.push(inheritedProvider);
|
|
58
|
+
workspaceTargets.push(Object.freeze({ workspaceId: id, path: providerTargetById.get(id), source: 'qualified-package-parent-workspace' }));
|
|
59
|
+
}
|
|
60
|
+
const state = selection.mode === 'none' ? 'not-requested' : (missing.length ? 'qualified' : 'not-needed');
|
|
38
61
|
return Object.freeze({
|
|
39
|
-
state
|
|
62
|
+
state,
|
|
63
|
+
providers: Object.freeze(providers),
|
|
64
|
+
providerState: 'qualified',
|
|
65
|
+
providerWorkspaceIds: Object.freeze(providers.map((item) => normalizeId(item.id))),
|
|
40
66
|
inherited: Object.freeze(inherited),
|
|
41
67
|
workspaceTargets: Object.freeze(workspaceTargets),
|
|
42
68
|
inspectionStatus: inspection.status,
|
|
43
69
|
missingWorkspaceIds: Object.freeze(missing.map((item) => normalizeId(item.workspaceId))),
|
|
70
|
+
selectionMode: selection.mode,
|
|
71
|
+
requestedWorkspaceIds: selection.ids,
|
|
44
72
|
workspaceAliases: Object.freeze([...workspaceAliases.entries()].map(([parentWorkspaceId, currentWorkspaceId]) => Object.freeze({ parentWorkspaceId, currentWorkspaceId }))),
|
|
45
|
-
boundary: '
|
|
73
|
+
boundary: 'Qualified package-parent Workspace snapshots may serve as read-only exact material providers, but complete Workspace carriage is reused only for explicitly selected package-parent Workspace ids. Package-parent carrier lineage alone never selects Workspace source. Explicit current Workspace roots take precedence by id, and explicit qualified workspace aliases may supersede a renamed parent-carrier Workspace without carrying stale duplicate source. Parent-carrier placement and lineage remain non-semantic.'
|
|
46
74
|
});
|
|
47
75
|
}
|
|
48
76
|
|
|
77
|
+
export function normalizePackageParentWorkspaceSelection(value = []) {
|
|
78
|
+
const raw = Array.isArray(value) ? value : [value];
|
|
79
|
+
const tokens = raw.flatMap((item) => String(item || '').split(',')).map((item) => String(item || '').trim()).filter(Boolean);
|
|
80
|
+
if (!tokens.length) return Object.freeze({ mode: 'none', ids: Object.freeze([]) });
|
|
81
|
+
if (tokens.some((item) => ['*', 'all'].includes(item.toLowerCase()))) return Object.freeze({ mode: 'all', ids: Object.freeze([]) });
|
|
82
|
+
return Object.freeze({ mode: 'explicit', ids: Object.freeze([...new Set(tokens.map(normalizeId).filter(Boolean))].sort()) });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function selectDeclaredPackageParentWorkspaceBindings(declared = [], selection = normalizePackageParentWorkspaceSelection()) {
|
|
86
|
+
const normalized = [...(declared || [])]
|
|
87
|
+
.map((item) => Object.freeze({ ...item, workspaceId: normalizeId(item.workspaceId) }))
|
|
88
|
+
.filter((item) => item.workspaceId);
|
|
89
|
+
if (selection.mode === 'none') return Object.freeze([]);
|
|
90
|
+
if (selection.mode === 'all') return Object.freeze(normalized);
|
|
91
|
+
const byId = new Map(normalized.map((item) => [item.workspaceId, item]));
|
|
92
|
+
for (const id of selection.ids || []) if (!byId.has(id)) throw new Error(`portable.handoff-manufacture.package-parent.workspace-selection.unresolved:${id}`);
|
|
93
|
+
return Object.freeze((selection.ids || []).map((id) => byId.get(id)));
|
|
94
|
+
}
|
|
95
|
+
|
|
49
96
|
function declaredPackageWorkspaceBindings(bundle = {}, inspection = null) {
|
|
50
97
|
const roots = (bundle.files || []).filter((file) => String(file.path || '') === RECIPIENT_V2_PACKAGE_V1_ROOT_PATH);
|
|
51
98
|
if (roots.length === 1) {
|
|
@@ -150,8 +197,25 @@ export function packageParentWorkspaceSupersededByCurrent(parentWorkspaceId = ''
|
|
|
150
197
|
return Boolean(replacement && currentWorkspaceIds.has(replacement));
|
|
151
198
|
}
|
|
152
199
|
|
|
153
|
-
function emptyReuse(state) {
|
|
154
|
-
|
|
200
|
+
function emptyReuse(state, options = {}) {
|
|
201
|
+
const selection = options.selection || normalizePackageParentWorkspaceSelection();
|
|
202
|
+
const workspaceAliases = options.workspaceAliases || new Map();
|
|
203
|
+
return Object.freeze({
|
|
204
|
+
state,
|
|
205
|
+
providers: Object.freeze([]),
|
|
206
|
+
providerState: String(options.providerState || 'unavailable'),
|
|
207
|
+
providerWorkspaceIds: Object.freeze([]),
|
|
208
|
+
inherited: Object.freeze([]),
|
|
209
|
+
workspaceTargets: Object.freeze([]),
|
|
210
|
+
inspectionStatus: String(options.inspectionStatus || ''),
|
|
211
|
+
missingWorkspaceIds: Object.freeze([]),
|
|
212
|
+
selectionMode: selection.mode,
|
|
213
|
+
requestedWorkspaceIds: selection.ids,
|
|
214
|
+
workspaceAliases: Object.freeze([...workspaceAliases.entries()].map(([parentWorkspaceId, currentWorkspaceId]) => Object.freeze({ parentWorkspaceId, currentWorkspaceId }))),
|
|
215
|
+
boundary: selection.mode === 'none'
|
|
216
|
+
? 'Package-parent carrier lineage was supplied without an explicit package-parent Workspace reuse selection; no parent Workspace source was carried. Qualified parent Workspace providers, when available, are read-only requirement-material sources only.'
|
|
217
|
+
: 'No explicitly selected package-parent Workspace provider reuse was required.'
|
|
218
|
+
});
|
|
155
219
|
}
|
|
156
220
|
function normalizeId(value = '') { return String(value || '').trim().toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, ''); }
|
|
157
221
|
function mediaTypeForPath(value = '') { const lower = String(value || '').toLowerCase(); if (lower.endsWith('.md')) return 'text/markdown'; if (lower.endsWith('.json')) return 'application/json'; if (/\.(?:m?js|cjs)$/.test(lower)) return 'text/javascript'; if (lower.endsWith('.ts')) return 'text/typescript'; if (lower.endsWith('.css')) return 'text/css'; if (lower.endsWith('.html')) return 'text/html'; if (/\.(?:yml|yaml)$/.test(lower)) return 'text/yaml'; if (lower.endsWith('.txt')) return 'text/plain'; return 'application/octet-stream'; }
|