@tiinex/core 0.22.0 → 0.23.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/tooling/portable/adapters/cli/cli.run.js +6 -0
- package/src/tooling/portable/adapters/cli/cli.workspace-init.js +31 -0
- package/src/tooling/portable/draft/draft.create.js +7 -6
- package/src/tooling/portable/editor/editor.assistance.js +5 -7
- package/src/tooling/portable/workspace/workspace.initialize.js +80 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiinex/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.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,
|
|
@@ -140,7 +140,8 @@
|
|
|
140
140
|
"./tooling/portable/comparison/sourceFrontierComparison.js": "./src/tooling/portable/comparison/sourceFrontierComparison.js",
|
|
141
141
|
"./tooling/portable/adapters/node/sourceFrontierComparison.js": "./src/tooling/portable/adapters/node/sourceFrontierComparison.js",
|
|
142
142
|
"./tooling/portable/handoff/recoveryAcceptanceAudit.js": "./src/tooling/portable/handoff/recoveryAcceptanceAudit.js",
|
|
143
|
-
"./tooling/portable/grounding/grounding.delegationReadiness.js": "./src/tooling/portable/grounding/grounding.delegationReadiness.js"
|
|
143
|
+
"./tooling/portable/grounding/grounding.delegationReadiness.js": "./src/tooling/portable/grounding/grounding.delegationReadiness.js",
|
|
144
|
+
"./tooling/portable/workspace/workspace.initialize.js": "./src/tooling/portable/workspace/workspace.initialize.js"
|
|
144
145
|
},
|
|
145
146
|
"files": [
|
|
146
147
|
"src",
|
|
@@ -172,12 +173,12 @@
|
|
|
172
173
|
"type": "git",
|
|
173
174
|
"url": "git+https://github.com/Tiinex/core.git"
|
|
174
175
|
},
|
|
175
|
-
"gitHead": "
|
|
176
|
+
"gitHead": "0115723fd777d088edda56f7d9d1bef898b359ff",
|
|
176
177
|
"tiinexRelease": {
|
|
177
178
|
"policy": "tiinex.master-npm-release.v1",
|
|
178
|
-
"sourceCommit": "
|
|
179
|
-
"sourceTree": "
|
|
179
|
+
"sourceCommit": "0115723fd777d088edda56f7d9d1bef898b359ff",
|
|
180
|
+
"sourceTree": "066320bc97b3d3d16caac487ae56a34c039bb1e2",
|
|
180
181
|
"repository": "Tiinex/core",
|
|
181
|
-
"previousVersion": "0.
|
|
182
|
+
"previousVersion": "0.22.0"
|
|
182
183
|
}
|
|
183
184
|
}
|
|
@@ -9,6 +9,7 @@ import { commandInput } from './cli.command-input.js';
|
|
|
9
9
|
import { continueGroundWithHostResult } from './cli.ground-recovery.js';
|
|
10
10
|
import { groundContinuationOperationInput, materializeGroundWorkspaceCliOutput } from './cli.ground-materialize.js';
|
|
11
11
|
import { runCommonAuthorCli } from './cli.common-author.js';
|
|
12
|
+
import { runCommonWorkspaceInitCli } from './cli.workspace-init.js';
|
|
12
13
|
import { projectCommonCliDefaultOutput } from './cli.common-output.js';
|
|
13
14
|
|
|
14
15
|
export async function runPortableCli(argv = process.argv.slice(2), io = console, runtime = {}) {
|
|
@@ -27,6 +28,11 @@ export async function runPortableCli(argv = process.argv.slice(2), io = console,
|
|
|
27
28
|
writeJson(io, result, parsed.flags.compact !== true);
|
|
28
29
|
return result?.findingSummary?.counts?.error ? 2 : 0;
|
|
29
30
|
}
|
|
31
|
+
if (parsed.command === 'init-workspace') {
|
|
32
|
+
const result = await runCommonWorkspaceInitCli(parsed, runtime);
|
|
33
|
+
writeJson(io, result, parsed.flags.compact !== true);
|
|
34
|
+
return result?.findingSummary?.counts?.error ? 2 : 0;
|
|
35
|
+
}
|
|
30
36
|
const timingEnabled = Boolean(parsed.flags['phase-timing']);
|
|
31
37
|
const totalStartedAt = timingEnabled ? monotonicNowMs() : 0;
|
|
32
38
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { preparePortableWorkspaceInitialization } from '../../workspace/workspace.initialize.js';
|
|
4
|
+
|
|
5
|
+
export async function runCommonWorkspaceInitCli(parsed = {}) {
|
|
6
|
+
const root = path.resolve(String(parsed.positionals?.[0] || '').trim() || '.');
|
|
7
|
+
const flags = parsed.flags || {};
|
|
8
|
+
const schemaMaterialPath = String(flags['schema-material'] || '').trim();
|
|
9
|
+
const schemaMarkdown = schemaMaterialPath ? await readFile(path.resolve(schemaMaterialPath), 'utf8') : '';
|
|
10
|
+
const result = preparePortableWorkspaceInitialization({
|
|
11
|
+
title: String(flags.title || flags['workspace-title'] || '').trim(),
|
|
12
|
+
workspaceId: String(flags['workspace-id'] || '').trim(),
|
|
13
|
+
repository: String(flags.repository || '').trim(),
|
|
14
|
+
ref: String(flags.ref || '').trim(),
|
|
15
|
+
sourceKind: String(flags['source-kind'] || '').trim(),
|
|
16
|
+
rootPath: String(flags['root-path'] || '.').trim(),
|
|
17
|
+
authors: String(flags.authors || '').trim(),
|
|
18
|
+
schemaTarget: String(flags['schema-target'] || '').trim(),
|
|
19
|
+
schemaMarkdown,
|
|
20
|
+
sameRepositorySchema: flags['same-repository-schema'] === true
|
|
21
|
+
});
|
|
22
|
+
if (result.status !== 'ready') return Object.freeze({ ...result, root });
|
|
23
|
+
const target = path.resolve(root, ...String(result.path).split('/'));
|
|
24
|
+
const rel = path.relative(root, target);
|
|
25
|
+
if (!rel || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) throw new Error('portable.workspace-init.target-outside-root');
|
|
26
|
+
try { await access(target); throw new Error(`portable.workspace-init.target-exists:${result.path}`); }
|
|
27
|
+
catch (error) { if (String(error?.message || '').startsWith('portable.workspace-init.target-exists:')) throw error; }
|
|
28
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
29
|
+
await writeFile(target, result.markdown, { encoding: 'utf8', flag: 'wx' });
|
|
30
|
+
return Object.freeze({ ...result, root, writeReceipt: Object.freeze({ path: target, workspaceRelativePath: result.path, bytes: Buffer.byteLength(result.markdown, 'utf8') }) });
|
|
31
|
+
}
|
|
@@ -24,13 +24,14 @@ export function createPortableLocalDraft(input = {}, options = {}) {
|
|
|
24
24
|
const hasDeclaredParent = portableParentRecordHasAnyValue(parentRecord);
|
|
25
25
|
const hasCompleteParent = portableParentRecordIsComplete(parentRecord);
|
|
26
26
|
const exactParentQualification = qualifyPortableExactParent(parentRecord, transitionType);
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
27
|
+
const rootCreation = transitionType === 'create-artifact';
|
|
28
|
+
const localContinuityParentCompatible = !rootCreation && exactParentQualification.state === 'qualified-local-continuity';
|
|
29
|
+
const parentRenderable = rootCreation ? !hasDeclaredParent : exactParentQualification.state === 'qualified' || localContinuityParentCompatible;
|
|
30
|
+
const qualifiedParentRecord = rootCreation ? null : parentRenderable ? exactParentQualification.snapshot : null;
|
|
31
|
+
const genericParentRecord = rootCreation ? parentRecord : qualifiedParentRecord;
|
|
32
|
+
const exactRendererParentCompatible = rootCreation ? !hasDeclaredParent : exactParentQualification.state === 'qualified';
|
|
32
33
|
const exactRendererEligible = exactContract.status === 'ready' && exactRendererParentCompatible;
|
|
33
|
-
const localContinuityRendererEligible = exactContract.status === 'ready' && localContinuityParentCompatible;
|
|
34
|
+
const localContinuityRendererEligible = !rootCreation && exactContract.status === 'ready' && localContinuityParentCompatible;
|
|
34
35
|
if (!schemaId) findings.push(portableFinding('error', 'portable.draft-create.schema.required', 'Local draft creation requires a target schema id.'));
|
|
35
36
|
if (hasDeclaredParent && !hasCompleteParent) findings.push(portableFinding('error', 'portable.draft-create.parent.incomplete', 'A declared Parent must provide explicit Parent Schema authority, Trace (or an explicit parent id), and a recoverable Origin path. A kind label is not Parent Schema authority.', {
|
|
36
37
|
hasParentSchema: Boolean(parentRecord.schemaId || parentRecord.currentSchemaId),
|
|
@@ -47,7 +47,7 @@ function projectDocument(record = {}, records = [], lineageInspection = null) {
|
|
|
47
47
|
sourceSha256: sha256Hex(new TextEncoder().encode(markdown)),
|
|
48
48
|
replacementMarkdown: workspacePackagingRepair.markdown,
|
|
49
49
|
diagnosticCodes: workspacePackagingRepair.diagnosticCodes,
|
|
50
|
-
boundary: 'Repairs only a tiinex.workspace.v1 artifact whose replacement independently qualifies through the same exact registered Workspace contract and c14n-v2 self-integrity requirements used by Handoff package manufacture.
|
|
50
|
+
boundary: 'Repairs only a tiinex.workspace.v1 artifact whose replacement independently qualifies through the same exact registered Workspace contract and c14n-v2 self-integrity requirements used by Handoff package manufacture. Existing resolver-capable Current Schema references are preserved; permalink refresh is a separate resolution operation and must not be inferred from integrity repair.'
|
|
51
51
|
}));
|
|
52
52
|
const integrityRepair = deterministicIntegrityHygieneRepair(markdown, audit.findings || []);
|
|
53
53
|
const repairQualification = integrityRepair.state === 'ready'
|
|
@@ -99,13 +99,11 @@ function deterministicWorkspacePackagingRepair(record = {}, audit = {}, markdown
|
|
|
99
99
|
const bare = currentRaw === 'tiinex.workspace.v1';
|
|
100
100
|
if (!linked && !bare) return freeze({ state: 'unavailable' });
|
|
101
101
|
|
|
102
|
+
// Current Schema is portable source identity, not a registry token. Preserve any
|
|
103
|
+
// already-resolvable external permalink; hosts/Core resolution may later offer a
|
|
104
|
+
// separate permalink refresh only when the resolved schema bytes actually changed.
|
|
102
105
|
let candidate = source;
|
|
103
|
-
|
|
104
|
-
const schemaAuthorityUnqualified = (audit.findings || []).some((item) => String(item.code || '') === 'audit.schema-authority.unqualified');
|
|
105
|
-
if (linked && schemaAuthorityUnqualified) {
|
|
106
|
-
candidate = candidate.replace(currentMatches[0][0], `${currentMatches[0][1]}tiinex.workspace.v1`);
|
|
107
|
-
schemaReferenceChanged = true;
|
|
108
|
-
}
|
|
106
|
+
const schemaReferenceChanged = false;
|
|
109
107
|
|
|
110
108
|
const sealed = sealC14nV2Self(candidate);
|
|
111
109
|
if (sealed.state !== 'sealed' && sealed.state !== 'unchanged') return freeze({ state: 'unavailable' });
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { parseArtifactMarkdown } from '../../../artifacts/artifact.parse.js';
|
|
3
|
+
import { sealC14nV2Self, canonicalC14nV2SelfState } from '../../../integrity/integrity.c14nV2.js';
|
|
4
|
+
import { integrityMethodReferenceAuthorityForCreation, C14N_V2_METHOD_ID } from '../../../integrity/integrity.methodReference.js';
|
|
5
|
+
import { schemaReferenceAuthorityForRegisteredSchema } from '../../../schemas/creation.schemaReferences.js';
|
|
6
|
+
import { compilePortableSchemaContract } from '../schema/contract.compile.js';
|
|
7
|
+
import { workspaceValidate } from '../../../schemas/workspace/tiinex.workspace.v1.validate.js';
|
|
8
|
+
|
|
9
|
+
export const PORTABLE_WORKSPACE_INITIALIZATION_SCHEMA_ID = 'tiinex.portable.workspace-initialization.v1';
|
|
10
|
+
export const WORKSPACE_SCHEMA_RESOLUTION_REQUEST = Object.freeze({
|
|
11
|
+
schemaId: 'tiinex.workspace.v1',
|
|
12
|
+
repository: 'Tiinex/docs',
|
|
13
|
+
path: '.topics/.schemas/tiinex.workspace.v1.schema.md',
|
|
14
|
+
resolution: 'latest-commit-permalink-plus-exact-schema-bytes',
|
|
15
|
+
boundary: 'The host resolves bytes; Core validates schema identity and owns rendered Workspace semantics. A commit change alone is not schema-change evidence.'
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export function preparePortableWorkspaceInitialization(input = {}) {
|
|
19
|
+
const findings = [];
|
|
20
|
+
const title = clean(input.title || input.workspaceId || repositoryLeaf(input.repository) || 'Workspace');
|
|
21
|
+
const workspaceId = slug(input.workspaceId || repositoryLeaf(input.repository) || title);
|
|
22
|
+
const schemaTarget = String(input.schemaTarget || input.schemaResolution?.target || '').trim();
|
|
23
|
+
const schemaMarkdown = String(input.schemaMarkdown || input.schemaResolution?.markdown || '');
|
|
24
|
+
if (!schemaTarget || !schemaMarkdown) return Object.freeze({
|
|
25
|
+
schema: PORTABLE_WORKSPACE_INITIALIZATION_SCHEMA_ID,
|
|
26
|
+
status: 'needs-resolution',
|
|
27
|
+
resolutionRequest: WORKSPACE_SCHEMA_RESOLUTION_REQUEST,
|
|
28
|
+
findingSummary: summary([]),
|
|
29
|
+
findings: Object.freeze([])
|
|
30
|
+
});
|
|
31
|
+
if (!isResolvableSchemaTarget(schemaTarget, input.sameRepositorySchema === true)) findings.push(finding('error', 'workspace.initialize.schema-target.unresolvable', 'Workspace Current Schema must remain resolvable: use a same-repository relative target or an absolute permalink.', { schemaTarget }));
|
|
32
|
+
let compiled = null;
|
|
33
|
+
try { compiled = compilePortableSchemaContract(schemaMarkdown); }
|
|
34
|
+
catch (error) { findings.push(finding('error', 'workspace.initialize.schema-material.invalid', 'Resolved Workspace schema material could not be compiled.', { detail: String(error?.message || error || '') })); }
|
|
35
|
+
if (compiled && String(compiled.schemaId || '') !== 'tiinex.workspace.v1') findings.push(finding('error', 'workspace.initialize.schema-id.mismatch', 'Resolved schema material is not tiinex.workspace.v1.', { observed: compiled.schemaId || '' }));
|
|
36
|
+
|
|
37
|
+
const repository = clean(input.repository || '');
|
|
38
|
+
const ref = clean(input.ref || '');
|
|
39
|
+
const sourceKind = clean(input.sourceKind || (repository ? 'github-tree' : 'local-session'));
|
|
40
|
+
const rootPath = clean(input.rootPath || '.');
|
|
41
|
+
const createdAt = timestamp(input.createdAt || new Date());
|
|
42
|
+
const authors = clean(input.authors || 'local-user');
|
|
43
|
+
const envelopeTarget = String(schemaReferenceAuthorityForRegisteredSchema('tiinex.root.v1')?.preferredTarget || '').trim();
|
|
44
|
+
const integrityTarget = String(integrityMethodReferenceAuthorityForCreation(C14N_V2_METHOD_ID)?.preferredTarget || '').trim();
|
|
45
|
+
if (!envelopeTarget) findings.push(finding('error', 'workspace.initialize.root-schema.unresolved', 'Core could not resolve canonical tiinex.root.v1 authority.'));
|
|
46
|
+
if (!integrityTarget) findings.push(finding('error', 'workspace.initialize.integrity-method.unresolved', 'Core could not resolve canonical c14n-v2 validator authority.'));
|
|
47
|
+
if (findings.some((item) => item.severity === 'error')) return blocked(findings);
|
|
48
|
+
|
|
49
|
+
const entrypoint = repository ? `\n## Workspace Entrypoints\n\n### Repository source\n\n- Source Kind: ${sourceKind}\n- Repository: ${repository}${ref ? `\n- Ref: ${ref}` : ''}\n- Root Path: ${rootPath}\n- Repo Files Discovery: on\n` : '';
|
|
50
|
+
const unsigned = `# Continuity Context\n\n- Envelope Schema: [tiinex.root.v1](${envelopeTarget})\n- Current\n - Current Schema: [tiinex.workspace.v1](${schemaTarget})\n - Created At: ${createdAt}\n - Authors: ${authors}\n - Why: Establish an explicit portable Workspace entrypoint for this repository.\n - Summary: ${title} Workspace.\n - Status: active/local\n\n---\n\n# ${title}${entrypoint}\n# Continuity Integrity\n\n- [sha256-base64url-c14n-v2](${integrityTarget})\n - Towards: self\n - Value: pending\n`;
|
|
51
|
+
const sealed = sealC14nV2Self(unsigned);
|
|
52
|
+
if (sealed.state !== 'sealed') return blocked([finding('error', 'workspace.initialize.integrity-seal.failed', 'Core could not seal Workspace c14n-v2 self integrity.', { reason: sealed.reason || sealed.state })]);
|
|
53
|
+
const parsed = parseArtifactMarkdown(sealed.markdown);
|
|
54
|
+
for (const item of workspaceValidate(parsed) || []) if (item?.severity === 'error') findings.push(finding('error', item.code || 'workspace.initialize.workspace-validation', item.message || 'Workspace validation failed.'));
|
|
55
|
+
const integrity = canonicalC14nV2SelfState(sealed.markdown);
|
|
56
|
+
if (integrity.state !== 'verified') findings.push(finding('error', 'workspace.initialize.integrity-verification.failed', 'Generated Workspace self integrity did not verify.', { reason: integrity.reason || integrity.state }));
|
|
57
|
+
if (String(parsed?.envelope?.current?.schema?.id || '') !== 'tiinex.workspace.v1') findings.push(finding('error', 'workspace.initialize.current-schema.mismatch', 'Generated Workspace Current Schema identity is not tiinex.workspace.v1.'));
|
|
58
|
+
if (findings.some((item) => item.severity === 'error')) return blocked(findings);
|
|
59
|
+
return Object.freeze({
|
|
60
|
+
schema: PORTABLE_WORKSPACE_INITIALIZATION_SCHEMA_ID,
|
|
61
|
+
status: 'ready',
|
|
62
|
+
workspaceId,
|
|
63
|
+
path: `.topics/.workspaces/${workspaceId}.workspace.md`,
|
|
64
|
+
markdown: sealed.markdown,
|
|
65
|
+
schemaReference: Object.freeze({ schemaId: 'tiinex.workspace.v1', target: schemaTarget }),
|
|
66
|
+
repository: Object.freeze({ repository, ref, sourceKind, rootPath }),
|
|
67
|
+
findingSummary: summary(findings),
|
|
68
|
+
findings: Object.freeze(findings),
|
|
69
|
+
boundary: 'Core owns Workspace artifact semantics, schema-reference preservation, and integrity. Hosts only resolve requested external schema bytes and perform the explicit filesystem write.'
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function blocked(findings) { return Object.freeze({ schema: PORTABLE_WORKSPACE_INITIALIZATION_SCHEMA_ID, status: 'blocked', resolutionRequest: WORKSPACE_SCHEMA_RESOLUTION_REQUEST, findingSummary: summary(findings), findings: Object.freeze(findings) }); }
|
|
74
|
+
function finding(severity, code, message, evidence = {}) { return Object.freeze({ severity, code, message, source: PORTABLE_WORKSPACE_INITIALIZATION_SCHEMA_ID, evidence: Object.freeze({ ...evidence }) }); }
|
|
75
|
+
function summary(findings) { const counts={error:0,warning:0,info:0,total:findings.length}; for(const item of findings) if(counts[item.severity]!==undefined) counts[item.severity]+=1; return Object.freeze({status:counts.error?'blocked':counts.warning?'degraded':'clean',counts:Object.freeze(counts)}); }
|
|
76
|
+
function clean(value) { return String(value ?? '').trim().replace(/[\r\n]+/g, ' '); }
|
|
77
|
+
function slug(value) { const out=clean(value).toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-+|-+$/g,''); return out || 'workspace'; }
|
|
78
|
+
function repositoryLeaf(value) { const text=clean(value).replace(/\.git$/i,'').replace(/[\\/]+$/,''); return text.split(/[\\/]/).filter(Boolean).at(-1) || ''; }
|
|
79
|
+
function timestamp(value) { const date=value instanceof Date?value:new Date(value); if(Number.isNaN(date.getTime())) return clean(value); return date.toISOString().replace('T',' ').replace(/\.\d{3}Z$/,''); }
|
|
80
|
+
function isResolvableSchemaTarget(value, sameRepository) { if(sameRepository && !/^[a-z][a-z0-9+.-]*:/i.test(value) && !value.startsWith('/')) return true; try { const url=new URL(value); return ['https:','http:'].includes(url.protocol); } catch { return false; } }
|