@awebai/oats 0.24.12 → 0.25.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.
Files changed (52) hide show
  1. package/bin/oats.mjs +936 -2822
  2. package/docs/capabilities.md +136 -323
  3. package/docs/configuration.md +68 -533
  4. package/docs/design/2026-09-16-fresh-operator-walkthrough.md +19 -14
  5. package/docs/design/2026-09-16-portable-migration-evidence.md +2 -0
  6. package/docs/design/2026-09-16-portable-onboarding.md +4 -2
  7. package/docs/design/2026-09-20-redesign-program-board.md +1 -1
  8. package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +2 -0
  9. package/docs/design/2026-09-23-simplified-workspace-model.md +711 -0
  10. package/docs/design/2026-09-23-workspace-module-contracts.md +309 -0
  11. package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +65 -0
  12. package/docs/design/README.md +20 -8
  13. package/docs/design/operations-contract.md +1 -0
  14. package/docs/design/package-engine-contract.md +1 -1
  15. package/docs/design/package-runtime-api.md +1 -1
  16. package/docs/desktop-cli-api.md +386 -6
  17. package/docs/desktop-succession.md +3 -4
  18. package/docs/first-team.md +107 -224
  19. package/docs/implementation.md +6 -4
  20. package/docs/integrations.md +45 -44
  21. package/docs/knowledge-capability-authoring.md +10 -4
  22. package/docs/knowledge-migration.md +5 -4
  23. package/docs/knowledge-reference/package-craft.md +11 -3
  24. package/docs/knowledge.md +24 -8
  25. package/docs/layers.md +3 -3
  26. package/docs/oats-local.schema.json +50 -0
  27. package/docs/oats-membership.schema.json +23 -0
  28. package/docs/oats-workspace.schema.json +133 -48
  29. package/docs/official-marketplace.md +9 -6
  30. package/docs/packages.md +229 -440
  31. package/docs/rebuild-to-v2.md +233 -0
  32. package/docs/release-notes/v0.24.13.md +51 -0
  33. package/docs/release-notes/v0.25.0.md +99 -0
  34. package/docs/soul.schema.json +41 -68
  35. package/docs/souls-and-instances.md +175 -108
  36. package/docs/workspace-adoption.md +70 -345
  37. package/docs/workspaces.md +429 -119
  38. package/lib/core.mjs +419 -55
  39. package/lib/instance-resolution.mjs +312 -0
  40. package/lib/materialize.mjs +580 -0
  41. package/lib/packages.mjs +501 -1273
  42. package/lib/remote.mjs +639 -0
  43. package/lib/resolve.mjs +576 -0
  44. package/lib/schedule.mjs +194 -34
  45. package/lib/workspace.mjs +635 -0
  46. package/package.json +1 -1
  47. package/lib/portable-migration-artifacts.mjs +0 -135
  48. package/lib/portable-migration-evidence.mjs +0 -305
  49. package/lib/portable-migration-store.mjs +0 -199
  50. package/lib/portable-migration.mjs +0 -104
  51. package/lib/portable-onboarding-acceptance.mjs +0 -66
  52. package/lib/setup-expert-source.mjs +0 -100
@@ -1,104 +0,0 @@
1
- /** Pure planning over literal historical evidence. No filesystem access, writes,
2
- * provider execution, source resolution, approval transfer or publication. */
3
- import { canonicalJson, compareUtf8 } from "./portable-values.mjs";
4
- import { jsonIntegrity } from "./portable-digest.mjs";
5
- import { validatePortableMigrationInventory } from "./portable-migration-evidence.mjs";
6
- import { oatsError } from "./errors.mjs";
7
-
8
- export const MIGRATION_PLAN_VERSION = 1;
9
- const originFor = (receipt, pointer = "") => receipt.state === "absent" ? [] : [{ kind: "migration-evidence",
10
- document: { kind: "deployment", path: receipt.path, integrity: receipt.integrity }, pointer }];
11
- const unresolved = (code, reason, receipt, pointer = "") => ({ pointer, code, origins: originFor(receipt, pointer), reason });
12
-
13
- function lockPlan(lock) {
14
- const target = { kind: "selection-lock", id: lock.path };
15
- if (lock.state === "absent") return { target, status: "unknown", action: "hold", evidence: [],
16
- unresolved: [unresolved("migration-held", "the historical lock file is absent", lock)] };
17
- const evidence = originFor(lock);
18
- if (lock.state === "invalid" || lock.format === "unknown") return { target, status: "unknown", action: "hold", evidence,
19
- unresolved: [unresolved("invalid-lock", "the literal historical lock is malformed or unsupported", lock)] };
20
- if (lock.format === "portable-v3") return { target, status: "partial", action: "verify", evidence,
21
- unresolved: [unresolved("migration-required", "the current lock still needs its authoritative lock-v3 reader; a lock is not historical dispatch authority", lock)] };
22
- const empty = lock.rowCounts.packages === 0 && lock.rowCounts.capabilities === 0;
23
- return { target, status: empty ? "unknown" : "partial", action: "hold", evidence,
24
- unresolved: [
25
- ...(lock.semanticVerification === "verified" ? [] : [unresolved("migration-required", "the strict legacy lock reader must verify every row without reinterpretation", lock)]),
26
- unresolved("migration-held", "a deployment lock does not prove which revision a particular home or job used", lock),
27
- unresolved("approval-required", "legacy trust is evidence only and cannot grant new-format exact-artifact approval", lock),
28
- ] };
29
- }
30
-
31
- function homePlan(home) {
32
- const target = { kind: "instance-home", id: home.path };
33
- const receipts = [home.document, home.cleanup.document].filter((entry) => entry.state !== "absent");
34
- const evidence = receipts.flatMap((entry) => originFor(entry));
35
- if (!receipts.length) return { target, status: "unknown", action: "hold", evidence: [],
36
- unresolved: [unresolved("migration-held", "the named home has no instance or quarantine metadata", home.document)] };
37
- if (home.executionBinding.state === "valid-shape") return { target, status: "partial", action: "verify", evidence,
38
- preserve: { executionBinding: home.executionBinding.value },
39
- unresolved: [
40
- unresolved("migration-required", "the referenced captured resolution and all retained inputs must be verified before adoption", home.document, "/executionBinding"),
41
- unresolved("approval-required", "current exact-artifact approval and action-specific readiness remain separate", home.document, "/executionBinding"),
42
- ] };
43
- const hasRuntime = home.capabilities.length || home.cleanup.capabilities.length;
44
- const invalid = home.document.state === "invalid" || home.cleanup.document.state === "invalid" || home.executionBinding.state === "invalid";
45
- return { target, status: hasRuntime && !invalid ? "partial" : "unknown", action: "hold", evidence,
46
- unresolved: [
47
- ...(invalid ? [unresolved("invalid-evidence", "historical home metadata is malformed or has an invalid captured binding", home.document)] : []),
48
- unresolved("migration-held", "the home does not carry a verified complete captured resolution", home.document),
49
- unresolved("migration-held", "historical source revision, full managed resources and helper closure are not established by home metadata", home.document),
50
- unresolved("approval-required", "recorded legacy trust cannot authorize a reconstructed owner-exec artifact", home.document),
51
- ...(home.cleanup.document.state !== "absent" ? [unresolved("migration-held", "cleanup obligations must remain retryable and cannot be discarded during migration", home.cleanup.document)] : []),
52
- ] };
53
- }
54
-
55
- function executionCandidates(job) {
56
- return [
57
- ["definition", job.execution],
58
- ["attempt", job.attempt],
59
- ["lastRun", job.lastRun],
60
- ].filter(([, value]) => value.state !== "absent");
61
- }
62
-
63
- function schedulePlan(schedule, job) {
64
- const target = { kind: "scheduled-job", id: `${schedule.path}:${job.id}` };
65
- const evidence = [schedule.definitions, schedule.state].filter((entry) => entry.state !== "absent").flatMap((entry) => originFor(entry));
66
- const executions = executionCandidates(job), valid = executions.filter(([, value]) => value.state === "valid-shape");
67
- const invalid = executions.some(([, value]) => value.state === "invalid") || !!job.problems?.length
68
- || schedule.definitions.state === "invalid" || schedule.state.state === "invalid";
69
- const unresolvedCustody = [job.attempt.state, job.lastRun.state].some((state) => state === "legacy" || state === "invalid");
70
- if (unresolvedCustody) return { target, status: "unknown", action: "hold", evidence,
71
- unresolved: [
72
- ...(invalid ? [unresolved("invalid-evidence", "the admitted or unknown attempt evidence is malformed", schedule.state)] : []),
73
- unresolved("migration-held", "an existing admitted or unknown historical attempt has no valid captured execution; preserve it exactly and require owner reconciliation", schedule.state),
74
- ] };
75
- if (valid.length) {
76
- const preserve = { executions: valid.map(([source, value]) => ({ source, ...value })) };
77
- return { target, status: "partial", action: "preserve", evidence, preserve,
78
- unresolved: [
79
- ...(invalid ? [unresolved("invalid-evidence", "one or more schedule records disagree with the valid captured authority and must remain held", schedule.state)] : []),
80
- unresolved("migration-required", "each referenced resolution and admitted execution must be verified without rebinding", schedule.state),
81
- ] };
82
- }
83
- const hasAttempt = job.attempt.state !== "absent" || job.lastRun.state !== "absent";
84
- return { target, status: "unknown", action: "hold", evidence,
85
- unresolved: [unresolved("migration-held", hasAttempt
86
- ? "the admitted or unknown historical attempt has no valid captured execution; preserve it exactly and require owner reconciliation"
87
- : "the legacy schedule has no captured software authority", schedule.state)] };
88
- }
89
-
90
- /** Produce a deterministic no-write plan. No target can become reconstructed at
91
- * this stage: reconstruction requires the dedicated historical verifier. */
92
- export function planPortableMigration(inventory) {
93
- validatePortableMigrationInventory(inventory);
94
- const targets = [
95
- ...inventory.locks.map(lockPlan),
96
- ...inventory.homes.map(homePlan),
97
- ...inventory.schedules.flatMap((schedule) => schedule.jobs.map((job) => schedulePlan(schedule, job))),
98
- ].sort((a, b) => compareUtf8(`${a.target.kind}:${a.target.id}`, `${b.target.kind}:${b.target.id}`));
99
- if (targets.some((entry) => entry.status === "reconstructed")) throw oatsError("invalid-evidence", "unverified planner output cannot claim reconstruction");
100
- const plan = { schemaVersion: MIGRATION_PLAN_VERSION, inventory: jsonIntegrity(inventory), readyToApply: false,
101
- effects: { writes: false, providerExecution: false, sessionChanges: false, scheduleChanges: false, trustTransfer: false }, targets };
102
- canonicalJson(plan);
103
- return plan;
104
- }
@@ -1,66 +0,0 @@
1
- /** Fresh setup acceptance boundary. Read-only comparison and explicit mutation
2
- * handoff only; all source discovery and request construction remain owned by
3
- * portable-onboarding. */
4
- import { canonicalJson, freezeJson } from "./portable-values.mjs";
5
- import { sameIdentity } from "./portable-identity.mjs";
6
- import { buildFreshPreparationRequest, recheckFreshOnboarding } from "./portable-onboarding.mjs";
7
- import { oatsError } from "./errors.mjs";
8
-
9
- export const PORTABLE_ONBOARDING_ACCEPTANCE_VERSION = 1;
10
-
11
- /** Prove two issued/ready inspections name one unchanged source while retaining
12
- * distinct organization and standalone work/context facts. */
13
- export function compareFreshSourceAcceptance({ organization, standalone }) {
14
- const organizationRequest = buildFreshPreparationRequest(organization), standaloneRequest = buildFreshPreparationRequest(standalone);
15
- if (!organization.workspace || organization.context.kind !== "workspace") throw oatsError("invalid-declaration", "organization acceptance requires an explicit workspace context");
16
- if (standalone.workspace !== null || standalone.context.kind !== "standalone") throw oatsError("invalid-declaration", "standalone acceptance requires an explicit standalone context");
17
- if (standalone.workTarget.git.present) throw oatsError("invalid-declaration", "standalone acceptance work target must be explicitly non-Git");
18
- if (!sameIdentity(organization.source.identity, standalone.source.identity)) throw oatsError("source-identity-change", "acceptance inspections name different source identities");
19
- if (organization.source.revision.commit !== standalone.source.revision.commit
20
- || organization.source.exportPath !== standalone.source.exportPath
21
- || organization.source.definition !== standalone.source.definition) {
22
- throw oatsError("integrity-drift", "acceptance inspections do not name the same unchanged source export");
23
- }
24
- return freezeJson({ schemaVersion: PORTABLE_ONBOARDING_ACCEPTANCE_VERSION, status: "ready",
25
- source: { identity: organization.source.identity, commit: organization.source.revision.commit,
26
- exportPath: organization.source.exportPath, definition: organization.source.definition },
27
- organization: { context: organization.context, workTarget: organization.workTarget,
28
- preparation: organizationRequest.preparation },
29
- standalone: { context: standalone.context, workTarget: standalone.workTarget,
30
- preparation: standaloneRequest.preparation },
31
- effects: { writes: false, preparation: false, approval: false, enrollment: false } });
32
- }
33
-
34
- function approvalRequests(result) {
35
- const requests = [];
36
- for (const selection of Array.isArray(result?.selections) ? result.selections : []) {
37
- for (const capability of Array.isArray(selection.approvalRequired) ? selection.approvalRequired : []) {
38
- requests.push({ capability, artifactSet: selection.artifactSet, request: selection.request });
39
- }
40
- }
41
- return requests;
42
- }
43
-
44
- /** Recheck fresh deployment state immediately before an explicitly supplied
45
- * public preparation function. Missing integration/provisioning remains pending;
46
- * no stale inspection is permission to overwrite managed state. */
47
- export function prepareFreshOnboarding(inspection, options = {}, { prepareCapturedComposition } = {}) {
48
- canonicalJson(options);
49
- const built = buildFreshPreparationRequest(inspection, options);
50
- const preflight = recheckFreshOnboarding(inspection);
51
- const requestedBindings = built.preparation.operator?.bindings ?? null;
52
- if (preflight.deployment.state === "absent") return freezeJson({ schemaVersion: PORTABLE_ONBOARDING_ACCEPTANCE_VERSION,
53
- status: "pending", code: "fresh-deployment-provisioning-required", mutationAttempted: false,
54
- preparation: built.preparation, workTarget: built.workTarget, requestedBindings,
55
- resolution: null, approvalRequests: [], problems: [{ code: "fresh-deployment-provisioning-required", message: "provision the explicit fresh deployment path before preparation" }] });
56
- if (typeof prepareCapturedComposition !== "function") return freezeJson({ schemaVersion: PORTABLE_ONBOARDING_ACCEPTANCE_VERSION,
57
- status: "pending", code: "onboarding-integration-required", mutationAttempted: false,
58
- preparation: built.preparation, workTarget: built.workTarget, requestedBindings,
59
- resolution: null, approvalRequests: [], problems: [{ code: "onboarding-integration-required", message: "the public captured preparation adapter is not available" }] });
60
- const result = prepareCapturedComposition(built.preparation);
61
- if (!result || typeof result !== "object" || Array.isArray(result)) throw oatsError("invalid-resolution", "public preparation returned no structured result");
62
- return freezeJson({ schemaVersion: PORTABLE_ONBOARDING_ACCEPTANCE_VERSION,
63
- status: typeof result.status === "string" ? result.status : "pending", mutationAttempted: true,
64
- preparation: built.preparation, workTarget: built.workTarget, requestedBindings,
65
- resolution: result.resolution ?? null, approvalRequests: approvalRequests(result), problems: Array.isArray(result.problems) ? result.problems : [] });
66
- }
@@ -1,100 +0,0 @@
1
- /** Read the setup edition as data for a CLASSIC local bootstrap copy.
2
- * No provider code, workspace activation, enrollment or captured identity. */
3
- import { lstatSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync } from 'node:fs';
4
- import { dirname, join } from 'node:path';
5
- import { tmpdir } from 'node:os';
6
- import { fileURLToPath } from 'node:url';
7
- import { parsePortableSoul } from './portable-soul.mjs';
8
- import { parseRepositorySource, parsePortableSource } from './source-spec.mjs';
9
- import { createRepositoryTransaction } from './repository-observation.mjs';
10
- import { createWorkspaceDiscovery } from './workspace-discovery.mjs';
11
- import { packageIntegrity } from './core.mjs';
12
- import { oatsError } from './errors.mjs';
13
-
14
- export const SETUP_EXPERT = 'oats-setup-expert';
15
- export const SETUP_CAPABILITIES = Object.freeze(['oats.core', 'oats.setup']);
16
- const EXPORT = `souls/${SETUP_EXPERT}`;
17
- function validateEdition(bytes) {
18
- const { declaration: soul } = parsePortableSoul(bytes);
19
- const required = soul.requires || {}, caps = required.capabilities || {};
20
- if (soul.name !== SETUP_EXPERT || soul.work !== 'directory'
21
- || Object.keys(required).some(key => key !== 'capabilities')
22
- || Object.keys(caps).length !== 2 || SETUP_CAPABILITIES.some(id => caps[id]?.source !== 'repo:oats-package' || Object.keys(caps[id]).some(key => key !== 'source'))
23
- || ['knowledge', 'messaging', 'tasks'].some(slot => soul.defaults?.[slot] !== 'none')
24
- || Object.keys(soul.defaults || {}).some(key => !['knowledge', 'messaging', 'tasks'].includes(key))
25
- || soul.knowledge || soul.teams?.length || soul.resources?.length || soul.yolo === true || soul.backend || soul['launch-config']) {
26
- throw oatsError('needs-configuration', 'classic setup bootstrap needs the provider-independent directory edition with only oats.core/oats.setup; use explicit portable preparation for other requirements');
27
- }
28
- return soul;
29
- }
30
- function repositoryRequest(source) {
31
- if (typeof source !== 'string' || source.includes('#')) throw oatsError('invalid-source', '--workspace needs a Git repository source, optionally @revision, without a package fragment');
32
- try { return { source: parseRepositorySource(source).normalized }; }
33
- catch {
34
- const parsed = parsePortableSource(source);
35
- if (parsed.kind !== 'git') throw oatsError('invalid-source', '--workspace needs an explicit Git repository source');
36
- return { source: `git:${parsed.url}`, revision: parsed.selector };
37
- }
38
- }
39
-
40
- /** `packages["oats.framework"]` from the repository's own `package-catalog.json` at the observed
41
- * revision, or null when the file or entry is absent. Data only; the acquisition still verifies
42
- * bytes, and the edition/package integrity comparison still applies. */
43
- function frameworkCatalogEntry(transaction, observation) {
44
- const file = transaction.readFile(observation, 'package-catalog.json', { optional: true });
45
- if (!file) return null;
46
- let doc;
47
- try { doc = JSON.parse(file.bytes.toString('utf8')); } catch { throw oatsError('invalid-source', 'workspace package-catalog.json is not valid JSON'); }
48
- const entry = doc && typeof doc === 'object' && !Array.isArray(doc) && doc.packages && typeof doc.packages === 'object' && !Array.isArray(doc.packages)
49
- ? doc.packages['oats.framework'] : undefined;
50
- if (entry === undefined) return null;
51
- if (!entry || typeof entry !== 'object' || Array.isArray(entry) || typeof entry.url !== 'string' || typeof entry.ref !== 'string' || (entry.path !== undefined && typeof entry.path !== 'string')) {
52
- throw oatsError('invalid-source', 'workspace package-catalog.json has an invalid oats.framework entry');
53
- }
54
- return { url: entry.url, ref: entry.ref, ...(entry.path === undefined ? {} : { path: entry.path }), revision: observation.source.commit };
55
- }
56
-
57
- export function loadSetupExpertEdition(workspace, repositoryOptions = {}) {
58
- if (workspace === undefined) {
59
- const root = fileURLToPath(new URL(`../${EXPORT}/`, import.meta.url));
60
- return { declaration: validateEdition(readFileSync(join(root, 'soul.yaml'))), instructions: readFileSync(join(root, 'AGENTS.md'), 'utf8'),
61
- source: { kind: 'packaged-definition', captured: false }, packageIntegrity: null, catalogEntry: null };
62
- }
63
- const request = repositoryRequest(workspace), scratch = realpathSync(mkdtempSync(join(tmpdir(), 'oats-setup-source-'))), owned = lstatSync(scratch);
64
- let transaction;
65
- try {
66
- transaction = createRepositoryTransaction({ ...repositoryOptions, directory: scratch, accessContextKey: 'explicit-setup-source' });
67
- const discovery = createWorkspaceDiscovery(transaction), origin = { kind: 'operator', document: { kind: 'operator', id: 'oats-onboard' }, pointer: '/workspace' };
68
- const observed = transaction.observe(request.source, { ...request, origin });
69
- const workspaceDoc = transaction.readFile(observed, 'oats-workspace.yaml', { optional: true });
70
- const view = workspaceDoc ? discovery.readWorkspace({ ...request, origin }) : null;
71
- const reference = view?.parsed.imports.find(item => item.alias === SETUP_EXPERT)
72
- ?? { source: request.source, revision: observed.source.commit, soul: EXPORT, alias: SETUP_EXPERT };
73
- // A selected workspace import never falls back if its exact source fails.
74
- const imported = discovery.importSoul(reference, { origin });
75
- if (reference.adoption) throw oatsError('needs-configuration', 'classic setup copies an edition only; provider adoption values require the portable preparation path');
76
- const declaration = validateEdition(transaction.readFile(imported.observation, imported.definition).bytes);
77
- const projection = join(scratch, 'edition');
78
- transaction.materialize(imported.observation, imported.roots, projection);
79
- const soulRoot = join(projection, dirname(imported.definition)), body = join(soulRoot, 'AGENTS.md'), alias = join(soulRoot, 'CLAUDE.md');
80
- if (!lstatSync(body).isFile() || !lstatSync(alias).isSymbolicLink() || readlinkSync(alias) !== 'AGENTS.md'
81
- || readdirSync(soulRoot).some(name => !['soul.yaml', 'AGENTS.md', 'CLAUDE.md'].includes(name))) {
82
- throw oatsError('source-incomplete', 'classic setup edition must have canonical AGENTS.md/CLAUDE.md and no omitted private skill or knowledge trees');
83
- }
84
- // The workspace publishes the reviewed catalog: read the oats.framework entry at the WORKSPACE's
85
- // observed revision (falling back to the edition's own revision when the source has no workspace
86
- // document), so onboarding acquires the framework the workspace currently names instead of
87
- // whatever the kernel tarball snapshotted at its own tag. The edition/package integrity check
88
- // below still decides whether that acquisition matches the copied edition.
89
- const catalogEntry = frameworkCatalogEntry(transaction, observed) ?? frameworkCatalogEntry(transaction, imported.observation);
90
- return { declaration, instructions: readFileSync(body, 'utf8'), packageIntegrity: packageIntegrity(join(projection, 'oats-package')),
91
- catalogEntry,
92
- source: { kind: 'exported-edition-copy', source: imported.reference.source, revision: imported.observation.source.commit,
93
- path: imported.reference.soul, workspaceRevision: view?.source.commit ?? null, captured: false } };
94
- } finally {
95
- transaction?.close();
96
- const current = lstatSync(scratch);
97
- if (!current.isDirectory() || current.dev !== owned.dev || current.ino !== owned.ino) throw oatsError('source-unavailable', 'setup source scratch ownership changed');
98
- rmSync(scratch, { recursive: true });
99
- }
100
- }