@starci/hfs 4.0.9 → 4.0.10
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/LICENSE +21 -0
- package/lint/run.mjs +80 -7
- package/package.json +3 -2
- package/runtime/engine/admission.mjs +308 -0
- package/runtime/engine/canonical-json.mjs +10 -0
- package/runtime/engine/config.mjs +36 -44
- package/runtime/engine/db/blob.mjs +315 -0
- package/runtime/engine/db/ledger-paths.mjs +83 -0
- package/runtime/engine/db/ledger.mjs +1118 -0
- package/runtime/engine/db/machine-connection.mjs +135 -0
- package/runtime/engine/db/machine-schema.mjs +84 -0
- package/runtime/engine/db/machine.mjs +1367 -0
- package/runtime/engine/db/migrations/machine/0001-init.sql +924 -0
- package/runtime/engine/db/migrations/runtime/0001-init.sql +1108 -0
- package/runtime/engine/db/provider-reservations.mjs +101 -0
- package/runtime/engine/digest.mjs +16 -0
- package/runtime/engine/refuse.mjs +11 -0
- package/runtime/engine/secrets.mjs +130 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +10 -10
- package/runtime/knowledge/hfs/rules.yaml +8 -8
- package/runtime/modules/kernel/failure-codes.yaml +40 -0
- package/runtime/modules/models/registry.yaml +1 -39
- package/runtime/modules/models/runtimes.yaml +0 -6
- package/runtime/modules/ops/_labels.yaml +53 -0
- package/runtime/scripts/api/fs/lib.mjs +6 -0
- package/runtime/scripts/api/git/lib.mjs +2 -0
- package/runtime/scripts/api/node/lib.mjs +14 -0
- package/runtime/scripts/api/node/spawn-node.mjs +6 -0
- package/runtime/scripts/api/process/lib.mjs +111 -0
- package/runtime/scripts/api/process/owned-process.mjs +78 -0
- package/runtime/scripts/api/process/resolve-real-tool.mjs +46 -0
- package/runtime/scripts/api/process/run-program.mjs +7 -0
- package/runtime/scripts/api/process/stop-owned-process.mjs +9 -0
- package/runtime/scripts/api/sops/decrypt.mjs +9 -16
- package/runtime/scripts/api/sops/lib.mjs +230 -10
- package/runtime/scripts/api/sops/seal.mjs +8 -4
- package/runtime/scripts/connectors/lib.mjs +488 -0
- package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +1 -1
- package/runtime/scripts/hfs/secret.mjs +45 -11
- package/runtime/scripts/lib/clip.mjs +19 -0
- package/runtime/scripts/lib/display-names.mjs +259 -0
- package/runtime/scripts/lib/example-refs.mjs +158 -0
- package/runtime/scripts/lib/fs-kind.mjs +14 -4
- package/runtime/scripts/lib/json-schema.mjs +52 -0
- package/runtime/scripts/lib/mutation-fence.mjs +15 -0
- package/runtime/scripts/lib/path-key.mjs +4 -4
- package/runtime/scripts/lib/process-identity.mjs +6 -0
- package/runtime/scripts/lib/read-yaml.mjs +18 -0
- package/runtime/scripts/lib/redact.mjs +161 -0
- package/runtime/scripts/lib/sleep.mjs +7 -0
- package/runtime/scripts/lib/sops-envelope.mjs +54 -0
- package/runtime/scripts/lib/source-phrases.mjs +34 -0
- package/runtime/scripts/lib/sqlite.mjs +21 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Provider receipt SQL participates in the machine writer's existing fenced transactions.
|
|
2
|
+
|
|
3
|
+
export function providerReservationMethods({ need, parse, toJson, hex }) {
|
|
4
|
+
// Provider/account slots are shared by all five agent roles. A clock timeout is not exit evidence.
|
|
5
|
+
const providerReservationRow = (row) => row ? { id: row.id, fence: row.fence, attemptId: row.attempt_id,
|
|
6
|
+
provider: row.provider, account: row.account, model: row.model, role: row.role, state: row.state,
|
|
7
|
+
slots: row.slots, amountUnit: 'concurrent-launch', maxParallel: row.max_parallel,
|
|
8
|
+
handle: row.handle, pid: row.pid, launchIdentity: row.launch_identity, hostRequestId: row.host_request_id, createdAt: row.created_at, updatedAt: row.updated_at,
|
|
9
|
+
scope: parse(row.scope_json), estimate: parse(row.estimate_json), quota: parse(row.quota_json),
|
|
10
|
+
override: parse(row.override_json), releasedAt: row.released_at, proof: parse(row.proof_json) } : null;
|
|
11
|
+
function providerReservations(m, { provider = null, account = null, activeOnly = false } = {}) {
|
|
12
|
+
const where = [], args = [];
|
|
13
|
+
if (provider !== null) { where.push('provider=?'); args.push(provider); }
|
|
14
|
+
if (account !== null) { where.push('account=?'); args.push(account); }
|
|
15
|
+
if (activeOnly) where.push("state<>'released'");
|
|
16
|
+
return m.db.prepare(`SELECT * FROM provider_reservations${where.length ? ` WHERE ${where.join(' AND ')}` : ''} ORDER BY fence`).all(...args).map(providerReservationRow);
|
|
17
|
+
}
|
|
18
|
+
function providerReservationUsage(m, { provider, account = 'default' } = {}) {
|
|
19
|
+
const reservations = providerReservations(m, { provider, account, activeOnly: true });
|
|
20
|
+
return { running: reservations.length, reservations, observed: true };
|
|
21
|
+
}
|
|
22
|
+
/** Atomic last-slot admission. Replays preserve the original receipt; changed attempt meaning refuses. */
|
|
23
|
+
function reserveProvider(m, { provider, account = 'default', attemptId, role, model, maxParallel, scope = null, quota = null, estimate = null, override = null } = {}) {
|
|
24
|
+
need([provider, account, attemptId, model].every((value) => typeof value === 'string' && value.trim()), 'provider reservation needs provider, account, attemptId and concrete model');
|
|
25
|
+
need(typeof role === 'string' && role.trim(), 'provider reservation needs one agent role'); // The DDL owns the allowed role domain.
|
|
26
|
+
need(Number.isInteger(maxParallel) && maxParallel >= 0, 'provider reservation needs nonnegative integer maxParallel');
|
|
27
|
+
return m.transaction((db) => {
|
|
28
|
+
const existing = db.prepare('SELECT * FROM provider_reservations WHERE attempt_id=?').get(attemptId);
|
|
29
|
+
if (existing) {
|
|
30
|
+
if (existing.provider !== provider || existing.account !== account || existing.model !== model || existing.role !== role || existing.scope_json !== toJson(scope))
|
|
31
|
+
return { ok: false, reason: 'attempt-conflict', reservation: providerReservationRow(existing) };
|
|
32
|
+
return { ok: existing.state !== 'released', reason: existing.state === 'released' ? 'attempt-released' : 'reserved', reused: true, reservation: providerReservationRow(existing) };
|
|
33
|
+
}
|
|
34
|
+
const running = Number(db.prepare("SELECT count(*) n FROM provider_reservations WHERE provider=? AND account=? AND state<>'released'").get(provider, account).n);
|
|
35
|
+
if (running >= maxParallel) return { ok: false, reason: 'capacity', running, maxParallel };
|
|
36
|
+
const id = hex(16), at = m.now();
|
|
37
|
+
const result = db.prepare("INSERT INTO provider_reservations(id,attempt_id,provider,account,model,role,scope_json,state,max_parallel,quota_json,estimate_json,override_json,created_at,updated_at) VALUES(?,?,?,?,?,?,?,'reserved',?,?,?,?,?,?)")
|
|
38
|
+
.run(id, attemptId, provider, account, model, role, toJson(scope), maxParallel, toJson(quota), toJson(estimate), toJson(override), at, at);
|
|
39
|
+
db.prepare("INSERT INTO provider_reservation_events(reservation_id,at,from_state,to_state) VALUES(?,?,NULL,'reserved')").run(id, at);
|
|
40
|
+
return { ok: true, reason: 'reserved', reused: false, reservation: providerReservationRow(db.prepare('SELECT * FROM provider_reservations WHERE fence=?').get(result.lastInsertRowid)) };
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const fencedProviderRow = (db, { id, fence, attemptId } = {}) => {
|
|
44
|
+
const row = db.prepare('SELECT * FROM provider_reservations WHERE id=? AND fence=?').get(id ?? '', fence ?? -1);
|
|
45
|
+
return row && (attemptId === undefined || row.attempt_id === attemptId) ? row : null;
|
|
46
|
+
};
|
|
47
|
+
/** Observe launch progress under its original fence; unknown still occupies its slot. */
|
|
48
|
+
function markProviderReservation(m, { id, fence, attemptId, provider, account, model, role, state, launchIdentity = null, hostRequestId = null, handle = null, pid = null } = {}) {
|
|
49
|
+
need(['launching', 'live', 'unknown'].includes(state), 'provider reservation observation needs launching, live or unknown');
|
|
50
|
+
return m.transaction((db) => {
|
|
51
|
+
const row = fencedProviderRow(db, { id, fence, attemptId });
|
|
52
|
+
if (!row) return { ok: false, reason: 'fenced' };
|
|
53
|
+
if (Object.entries({ provider, account, model, role }).some(([key, value]) => value !== undefined && row[key] !== value)) return { ok: false, reason: 'identity-conflict' };
|
|
54
|
+
if (row.state === 'released') return { ok: false, reason: 'released' };
|
|
55
|
+
if (state === 'launching' && (typeof launchIdentity !== 'string' || !launchIdentity.trim())) return { ok: false, reason: 'launch-identity-required' };
|
|
56
|
+
if (launchIdentity !== null && row.launch_identity && row.launch_identity !== launchIdentity) return { ok: false, reason: 'launch-identity-conflict', reservation: providerReservationRow(row) };
|
|
57
|
+
if (hostRequestId !== null && row.host_request_id && row.host_request_id !== hostRequestId) return { ok: false, reason: 'host-request-conflict', reservation: providerReservationRow(row) };
|
|
58
|
+
if (state === 'launching' && row.state !== 'reserved' && row.state !== 'launching') return { ok: false, reason: 'state-regression' };
|
|
59
|
+
if ((row.handle && handle && row.handle !== handle) || (row.pid && pid && row.pid !== pid)) return { ok: false, reason: 'identity-conflict' };
|
|
60
|
+
const at = m.now();
|
|
61
|
+
db.prepare('UPDATE provider_reservations SET state=?,launch_identity=coalesce(launch_identity,?),host_request_id=coalesce(host_request_id,?),handle=coalesce(handle,?),pid=coalesce(pid,?),updated_at=? WHERE id=? AND fence=?')
|
|
62
|
+
.run(state, launchIdentity, hostRequestId, handle, pid, at, id, fence);
|
|
63
|
+
if (row.state !== state) db.prepare('INSERT INTO provider_reservation_events(reservation_id,at,from_state,to_state) VALUES(?,?,?,?)').run(id, at, row.state, state);
|
|
64
|
+
return { ok: true, reservation: providerReservationRow(db.prepare('SELECT * FROM provider_reservations WHERE id=?').get(id)) };
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** Only the launcher/reconciler's definitive matching exit/no-effect receipt releases capacity. */
|
|
68
|
+
function releaseProviderReservation(m, { id, fence, attemptId, proof } = {}) {
|
|
69
|
+
return m.transaction((db) => {
|
|
70
|
+
const row = fencedProviderRow(db, { id, fence, attemptId });
|
|
71
|
+
if (!row) return { ok: false, reason: 'fenced' };
|
|
72
|
+
if (row.state === 'released') return { ok: true, reused: true, reservation: providerReservationRow(row) };
|
|
73
|
+
const affirmation = proof?.hostAffirmation;
|
|
74
|
+
const reply = affirmation?.receipt, result = reply?.result;
|
|
75
|
+
const residual = result?.residualResources ?? result?.worker?.residualResources;
|
|
76
|
+
const identities = [result, result?.worker, result?.terminal].filter((value) => value && typeof value === 'object');
|
|
77
|
+
const hostNoEffect = reply && typeof reply === 'object' && !['unknown', 'outcome_unknown'].includes(result?.state)
|
|
78
|
+
&& !['unknown', 'outcome_unknown'].includes(result?.worker?.state)
|
|
79
|
+
&& !(residual && typeof residual === 'object' && Object.keys(residual).length > 0)
|
|
80
|
+
&& !identities.some((value) => ['agentTerminalHandle', 'terminalHandle', 'handle', 'pid', 'processId', 'dispatchId'].some((key) => value[key] !== undefined && value[key] !== null && value[key] !== ''))
|
|
81
|
+
&& (result?.failedStage !== undefined || result?.stage !== undefined || typeof reply.error?.code === 'string' && reply.error.code.trim());
|
|
82
|
+
const resolvedUnknown = row.state === 'unknown' && row.launch_identity && proof?.launchIdentity === row.launch_identity
|
|
83
|
+
&& row.host_request_id && affirmation?.requestId === row.host_request_id && affirmation.kind === 'worker-start-replay'
|
|
84
|
+
&& affirmation.replayed === true && affirmation.outcome === 'failed' && affirmation.effectState === 'none'
|
|
85
|
+
&& hostNoEffect;
|
|
86
|
+
const noEffect = proof?.kind === 'failed-before-launch' && proof.confirmed === true && !row.handle && !row.pid
|
|
87
|
+
&& (['reserved', 'launching'].includes(row.state) || resolvedUnknown);
|
|
88
|
+
const terminalEnded = row.handle && proof?.kind === 'closed' && proof.handle === row.handle
|
|
89
|
+
&& ['gone', 'disconnected'].includes(proof.terminalProof) && ['none', 'stopped'].includes(proof.processVerdict);
|
|
90
|
+
const processEnded = !row.handle && row.pid && proof?.kind === 'process-exited' && proof.pid === row.pid;
|
|
91
|
+
const ended = proof?.confirmed === true && (terminalEnded || processEnded)
|
|
92
|
+
&& (!row.pid || proof.pid == null || proof.pid === row.pid);
|
|
93
|
+
if (!noEffect && !ended) return { ok: false, reason: 'exit-unproven', reservation: providerReservationRow(row) };
|
|
94
|
+
const at = m.now();
|
|
95
|
+
db.prepare("UPDATE provider_reservations SET state='released',released_at=?,updated_at=?,proof_json=? WHERE id=? AND fence=?").run(at, at, toJson(proof), id, fence);
|
|
96
|
+
db.prepare("INSERT INTO provider_reservation_events(reservation_id,at,from_state,to_state,proof_json) VALUES(?,?,?,'released',?)").run(id, at, row.state, toJson(proof));
|
|
97
|
+
return { ok: true, reused: false, reservation: providerReservationRow(db.prepare('SELECT * FROM provider_reservations WHERE id=?').get(id)) };
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return { reserveProvider, providerReservations, providerReservationUsage, markProviderReservation, releaseProviderReservation };
|
|
101
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// digest.mjs — the one SHA-256 the runtime hashes with. A leaf: every file's integrity digest,
|
|
2
|
+
// every ledger input ref and every stamped asset is this function, so a byte stream and a file
|
|
3
|
+
// never disagree about what their digest is.
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
|
|
7
|
+
/** The lowercase hex SHA-256 of `value` (string, Buffer or TypedArray). */
|
|
8
|
+
export const sha256 = (value) => createHash('sha256').update(value).digest('hex');
|
|
9
|
+
/** The SHA-256 of the file's exact bytes; throws when the file cannot be read. */
|
|
10
|
+
export function sha256File(file){
|
|
11
|
+
// A numeric descriptor belongs to the caller; positional reads preserve its offset.
|
|
12
|
+
const owned=typeof file!=='number',fd=owned?fs.openSync(file,'r'):file;
|
|
13
|
+
const hash=createHash('sha256'),buffer=Buffer.alloc(1024*1024);
|
|
14
|
+
try{let at=0,n;while((n=fs.readSync(fd,buffer,0,buffer.length,at))>0){hash.update(buffer.subarray(0,n));at+=n;}return hash.digest('hex');}
|
|
15
|
+
finally{if(owned)fs.closeSync(fd);}
|
|
16
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// refuse.mjs — the refusal Error every verb raises: a message plus a stable `code` (and any extra fields the
|
|
2
|
+
// caller attaches). `refuse` builds it, `fail` throws it, `need` throws it when a required condition is false.
|
|
3
|
+
|
|
4
|
+
/** An Error carrying `code` and `extra` fields; callers `throw` it or hand it to a result row. */
|
|
5
|
+
export const refuse = (message, code, extra = {}) => Object.assign(new Error(message), { code, ...extra });
|
|
6
|
+
|
|
7
|
+
/** Throws the refusal `refuse` builds — the spelling used where refusing is the whole statement. */
|
|
8
|
+
export const fail = (message, code, extra = {}) => { throw refuse(message, code, extra); };
|
|
9
|
+
|
|
10
|
+
/** Throws `refuse(message, code)` when `ok` is falsy — the assertion spelling of a refusal. */
|
|
11
|
+
export const need = (ok, message, code) => { if (!ok) throw refuse(message, code); };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
/** The host-local credential filename, resolved only against a verified runtime main root. */
|
|
5
|
+
export const SECRET_ENV_FILE='secret.env';
|
|
6
|
+
/** The configured credential variable grammar shared by validation and secret resolution. */
|
|
7
|
+
export const ENV_NAME=/^[A-Z_][A-Z0-9_]{0,63}$/;
|
|
8
|
+
export const CREDENTIAL_FILE_MAX_BYTES=1024*1024;
|
|
9
|
+
const resolvedValues = new Set();
|
|
10
|
+
const remember = value => {
|
|
11
|
+
if (typeof value === 'string' && value.length >= 6) resolvedValues.add(value);
|
|
12
|
+
return value;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Filter credentials resolved by this process; the values never leave their resolution owner. */
|
|
16
|
+
export function redactResolvedSecrets(text) {
|
|
17
|
+
let clean = text;
|
|
18
|
+
for (const value of resolvedValues) if (clean.includes(value)) clean = clean.split(value).join('[redacted:resolved-secret]');
|
|
19
|
+
return clean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Check lexical availability only; blank and conventional template markers never prove provider validity. */
|
|
23
|
+
export function credentialPresent(value){
|
|
24
|
+
if(typeof value!=='string'||!value.trim())return false;
|
|
25
|
+
return !/^(?:CHANGE_ME|REPLACE_ME|PLACEHOLDER|TODO|<[^>]*>|YOUR_[A-Z0-9_]+)$/i.test(value.trim());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Read one configured credential file with a finite byte budget; no effects beyond the read. */
|
|
29
|
+
export function readSecretBytes(file){
|
|
30
|
+
const before=fs.lstatSync(file);
|
|
31
|
+
if(!before.isFile()||before.isSymbolicLink())throw new Error('credential file must be a regular file');
|
|
32
|
+
if(before.size>CREDENTIAL_FILE_MAX_BYTES)throw new Error('credential file exceeds the byte budget');
|
|
33
|
+
const fd=fs.openSync(file,fs.constants.O_RDONLY|(fs.constants.O_NOFOLLOW??0));
|
|
34
|
+
let bytes;
|
|
35
|
+
try{
|
|
36
|
+
const stat=fs.fstatSync(fd);
|
|
37
|
+
if(!stat.isFile()||stat.dev!==before.dev||stat.ino!==before.ino)throw new Error('credential file changed during resolution');
|
|
38
|
+
bytes=Buffer.alloc(CREDENTIAL_FILE_MAX_BYTES+1);
|
|
39
|
+
let count=0;
|
|
40
|
+
while(count<bytes.length){const n=fs.readSync(fd,bytes,count,bytes.length-count,null);if(n===0)break;count+=n;}
|
|
41
|
+
if(count>CREDENTIAL_FILE_MAX_BYTES)throw new Error('credential file exceeds the byte budget');
|
|
42
|
+
return bytes.subarray(0,count);
|
|
43
|
+
}catch(error){bytes?.fill(0);throw error;}finally{fs.closeSync(fd);}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const readCredentialFile = file => { const bytes=readSecretBytes(file); try{return bytes.toString('utf8');}finally{bytes.fill(0);} };
|
|
47
|
+
|
|
48
|
+
/** Parse a dotenv file (KEY=VALUE lines, # comments, optional export/quotes). An absent file is {}. */
|
|
49
|
+
export function readDotenv(file){
|
|
50
|
+
let text='';try{text=readCredentialFile(file);}catch(error){if(error?.code==='ENOENT')return {};throw error;}
|
|
51
|
+
const out={};
|
|
52
|
+
for(const line of text.split(/\r?\n/)){
|
|
53
|
+
const m=line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
|
|
54
|
+
if(!m)continue;
|
|
55
|
+
let value=m[2];
|
|
56
|
+
if(value.length>=2&&(value[0]==='"'||value[0]==="'")&&value.at(-1)===value[0])value=value.slice(1,-1);
|
|
57
|
+
out[m[1]]=value;
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolve a configured variable or its supported NAME_FILE custody pointer. An explicitly supplied
|
|
64
|
+
* blank variable disables the value; it must not resurrect a file value. Never print the result.
|
|
65
|
+
*/
|
|
66
|
+
export function connectorSecret(name,env=process.env){
|
|
67
|
+
if(typeof name!=='string'||!ENV_NAME.test(name))return null;
|
|
68
|
+
if(Object.hasOwn(env,name))return typeof env[name]==='string'?remember(env[name].trim()||null):null;
|
|
69
|
+
const pointer=typeof env[`${name}_FILE`]==='string'?env[`${name}_FILE`].trim():'';
|
|
70
|
+
if(!pointer)return null;
|
|
71
|
+
try{const value=readCredentialFile(pointer).trim();return remember(value||null);}catch{return null;}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Load the verified runtime main's local credentials below the actual environment without mutating
|
|
76
|
+
* either input. The caller owns repository identity; this base-tier reader never infers a root from cwd.
|
|
77
|
+
* Absence is allowed so the action-specific gate can name only the credentials that action requires.
|
|
78
|
+
*/
|
|
79
|
+
export function secretEnv(verifiedRuntimeRoot,env=process.env){
|
|
80
|
+
if(typeof verifiedRuntimeRoot!=='string'||!path.isAbsolute(verifiedRuntimeRoot))
|
|
81
|
+
throw new TypeError('secretEnv requires an absolute verified runtime root');
|
|
82
|
+
if(env===null||typeof env!=='object'||Array.isArray(env))throw new TypeError('secretEnv requires an environment mapping');
|
|
83
|
+
const root=fs.lstatSync(verifiedRuntimeRoot);
|
|
84
|
+
if(!root.isDirectory()||root.isSymbolicLink())throw new Error('secretEnv runtime root must be a regular directory');
|
|
85
|
+
const file=path.join(verifiedRuntimeRoot,SECRET_ENV_FILE);
|
|
86
|
+
let stat;
|
|
87
|
+
try{stat=fs.lstatSync(file);}catch(error){if(error?.code==='ENOENT')return {...env};throw error;}
|
|
88
|
+
if(!stat.isFile()||stat.isSymbolicLink())throw new Error('secret.env must be a regular file');
|
|
89
|
+
const local = readDotenv(file);
|
|
90
|
+
const resolved = {...local,...env};
|
|
91
|
+
for (const name of Object.keys(local)) if (typeof resolved[name] === 'string') remember(resolved[name]);
|
|
92
|
+
return resolved;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Select a supplied age identity without inventing custody or running a key tool. Inline execution awaits its owned isolation capability. */
|
|
96
|
+
export function sopsIdentityEnv(env,{identity=null,required=false,platform=process.platform}={}){
|
|
97
|
+
const refuse=(reason,message)=>{
|
|
98
|
+
const error=new Error(`SOPS identity [${reason}]: ${message}`);
|
|
99
|
+
error.name='SopsIdentityRefusal';error.identityRefusal=reason;
|
|
100
|
+
return {mode:'refused',env:null,error};
|
|
101
|
+
};
|
|
102
|
+
if(!env||typeof env!=='object'||Array.isArray(env))return refuse('invalid-environment','an environment mapping is required');
|
|
103
|
+
const names=name=>Object.keys(env).filter(key=>platform==='win32'?key.toUpperCase()===name:key===name);
|
|
104
|
+
const inline=names('SOPS_AGE_KEY'),files=names('SOPS_AGE_KEY_FILE');
|
|
105
|
+
if(inline.length>1||files.length>1)return refuse('ambiguous-environment','multiple Windows aliases select an age identity');
|
|
106
|
+
if(inline.length){
|
|
107
|
+
const name=inline[0];
|
|
108
|
+
if(!credentialPresent(env[name]))return refuse('disabled-inline','SOPS_AGE_KEY is explicitly empty or invalid');
|
|
109
|
+
remember(env[name].trim());
|
|
110
|
+
return {...refuse('inline-context-unqualified','the supplied SOPS_AGE_KEY awaits an owned selected-identity isolation capability; no SOPS process was launched'),inlineName:name};
|
|
111
|
+
}
|
|
112
|
+
let file=identity;
|
|
113
|
+
if(files.length){
|
|
114
|
+
const value=env[files[0]];
|
|
115
|
+
if(!credentialPresent(value))return refuse('disabled-file','SOPS_AGE_KEY_FILE is explicitly empty or invalid');
|
|
116
|
+
if(identity!==null&&identity!==undefined&&identity!==value)return refuse('conflicting-files','two different original identity files were explicitly selected');
|
|
117
|
+
file=value;
|
|
118
|
+
}
|
|
119
|
+
if(file!==null&&file!==undefined){
|
|
120
|
+
if(!credentialPresent(file))return refuse('invalid-file','the original identity file selection is empty or invalid');
|
|
121
|
+
if(!path.isAbsolute(file))return refuse('identity-file-location-unqualified','select the original identity by its absolute path; caller and SOPS working directories may differ');
|
|
122
|
+
try{
|
|
123
|
+
const stat=fs.lstatSync(file);
|
|
124
|
+
if(!stat.isFile()||stat.isSymbolicLink())return refuse('identity-file-unavailable','the explicitly selected original identity must be a regular non-symlink file');
|
|
125
|
+
}catch{return refuse('identity-file-unavailable','the explicitly selected original identity file is unavailable');}
|
|
126
|
+
return {mode:'file',env:{...env,SOPS_AGE_KEY_FILE:file},error:null};
|
|
127
|
+
}
|
|
128
|
+
if(required)return refuse('identity-not-supplied','supply SOPS_AGE_KEY locally or select the original SOPS_AGE_KEY_FILE; no home default was inferred');
|
|
129
|
+
return {mode:'public-recipient',env:{...env},error:null};
|
|
130
|
+
}
|
|
@@ -22,68 +22,68 @@ pins:
|
|
|
22
22
|
source: packages/heroicons/package.json
|
|
23
23
|
why: 'StarCi custom Heroicons-compatible cuts; eslint-canon-fe (icon.mjs) admits @starci/heroicons/24/outline and /16/solid as icon sources, so an app that uses them gets this exact version.'
|
|
24
24
|
'@starci/grammar':
|
|
25
|
-
version: 0.8.
|
|
25
|
+
version: 0.8.3
|
|
26
26
|
group: starci
|
|
27
27
|
install: registry
|
|
28
28
|
side: fe
|
|
29
29
|
source: packages/grammar/package.json
|
|
30
30
|
why: the product front ends pinned older versions (0.4.11, 0.5.0, 0.5.1); the runtime source is 0.8.0 and wins for a @starci package (the brand layer sets `--font-sans` and `--font-mono`; the grammar reads them; 0.7.2 added the Input tel kind and IconButton disclosure props).
|
|
31
31
|
'@starci/eslint-canon-be':
|
|
32
|
-
version: 3.0.
|
|
32
|
+
version: 3.0.10
|
|
33
33
|
group: starci
|
|
34
34
|
install: registry
|
|
35
35
|
side: be
|
|
36
36
|
source: packages/eslint/be/package.json
|
|
37
37
|
why: '3.0.3: its bundled canon-pins copy pins stylelint-canon 2.0.2 and hfs 4.0.3; no rule changed. 3.0.2: its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root (app.starcistacks, app.sops) and pin hfs 4.0.2. 3.0.1: its bundled canon-pins copy pins hfs 4.0.1. 3.0.0: `loadHfs(import.meta.url)` of be/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the be side; the project graph is built per side. 2.0.0 (C0 release): starciBeConfig({ hfs: loadHfs(import.meta.url) }) typed factory and the BE-CONVENTION laws.'
|
|
38
38
|
'@starci/eslint-canon-fe':
|
|
39
|
-
version: 8.0.
|
|
39
|
+
version: 8.0.10
|
|
40
40
|
group: starci
|
|
41
41
|
install: registry
|
|
42
42
|
side: fe
|
|
43
43
|
source: packages/eslint/fe/package.json
|
|
44
44
|
why: '8.0.3: its bundled canon-pins copy pins stylelint-canon 2.0.2 and hfs 4.0.3; no rule changed. 8.0.2: its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root (app.starcistacks, app.sops) and pin hfs 4.0.2. 8.0.1: its bundled canon-pins copy pins hfs 4.0.1. 8.0.0: `loadHfs(import.meta.url)` of fe/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the fe side; the project graph is built per side. 7.0.0: the front end has no tests (FE_NO_TESTS R97 in hfs); no-vietnamese-in-source (R91).'
|
|
45
45
|
'@starci/stylelint-canon':
|
|
46
|
-
version: 2.0.
|
|
46
|
+
version: 2.0.5
|
|
47
47
|
group: starci
|
|
48
48
|
install: registry
|
|
49
49
|
side: fe
|
|
50
50
|
source: packages/stylelint/package.json
|
|
51
51
|
why: '2.0.2: no-class-selector refuses a compound class selector (`div.card`), which the 2.0.1 pattern let through. 2.0.1 accepts `--font-sans` and `--font-mono` in the brand layer (the vocabulary follows the grammar 0.8.0). 2.0.0 adds status-contrast (HeroUI soft pairs measured per theme), fixes brand-layer-shape on the shared :root,.light,.dark block and the info soft pair, and derives appTokens with loadAppTokens(import.meta.url) for the managed one-line stylelint.config.mjs.'
|
|
52
52
|
'@starci/tsconfig':
|
|
53
|
-
version: 2.0.
|
|
53
|
+
version: 2.0.3
|
|
54
54
|
group: starci
|
|
55
55
|
install: registry
|
|
56
56
|
side: both
|
|
57
57
|
source: packages/tsconfig/package.json
|
|
58
58
|
'@starci/prettier-config':
|
|
59
|
-
version: 1.0.
|
|
59
|
+
version: 1.0.3
|
|
60
60
|
group: starci
|
|
61
61
|
install: registry
|
|
62
62
|
side: both
|
|
63
63
|
source: packages/prettier-config/package.json
|
|
64
64
|
'@starci/jest-preset':
|
|
65
|
-
version: 2.2.
|
|
65
|
+
version: 2.2.5
|
|
66
66
|
group: starci
|
|
67
67
|
install: registry
|
|
68
68
|
side: be
|
|
69
69
|
source: packages/jest-preset/package.json
|
|
70
70
|
why: '2.2.4: the world runner runs up to min(--maxWorkers, slots) world files at once, each in its own process bound to one data slot of the test world (STARCI_TEST_WORLD_SLOT), and reads test-world state protocol 2 only: pinned together with @starci/test-world 1.1.0, any other state file is JEST_PRESET_WORLD_PAIR_MISMATCH. 2.2.3: republished after the move (no preset change). 2.2.2: the unit run writes the lcov Sonar and Codecov import (services only); 2.2.0: the integration, e2e and contract projects run every spec file in a worker process of its own (world-runner.cjs), so a process-global framework registry (@nestjs/graphql type metadata) never leaks between e2e files; 2.1.0: four projects on the test world, the unit kit (mockEntityManager, fakeTransaction, fakeIds, FakeClock, Outcome matchers) and the recordingOutbox claim side.'
|
|
71
71
|
'@starci/test-world':
|
|
72
|
-
version: 1.2.
|
|
72
|
+
version: 1.2.1
|
|
73
73
|
group: starci
|
|
74
74
|
install: registry
|
|
75
75
|
side: be
|
|
76
76
|
source: packages/test-world/package.json
|
|
77
77
|
why: '1.2.0: Kafka as real own infra (the one digest-pinned apache/kafka KRaft image, a broker listener and proxy per slot, slot-prefixed topics, consumer groups and client ids) and schema-per-context Postgres (a schema and a login role per context in a shared database); its CLI main is exported at ./cli for starci app stack, and the former starci app stack bin is removed. 1.1.0: per-worker data slots: a run provisions min(jest workers, workers cap, default 2) slots, each its own namespace (<ns>_w<k>) with its own databases, Keycloak realm, leased Redis DB, bucket/collection/topic prefixes, fakes host, toxiproxy proxies and outage lock, so world files run in parallel; state file protocol 2, paired exactly with @starci/jest-preset 2.2.4 (TEST_WORLD_PAIR_MISMATCH otherwise). 1.0.6: republished after the move (no library change). 1.0.5: a modules world boots real peer apps beside its modules, world.apps.<name>.during(fn) is the outage of a peer app, and w.keycloak.clientSecret(client) answers the run-generated secret of a confidential realm client. 1.0.4: world.resolve and scope.resolve take any Nest token (class, string or symbol). 1.0.3: the globalSetup registers the path aliases as TypeScript resolves them (the extends chain, paths from the config that declares them, the effective baseUrl), so a tests tsconfig that only extends the side config loads the declaration. 1.0.2: every path a declaration names (`stack`, seeds, the realm, Dockerfiles) resolves from the app root (the directory of hfs.json), where .starcistacks lives, never from the be side. The shared e2e library of every back end (R47, R48): the warm stack behind toxiproxy, the network-edge fakes, the Nest boot, the typed useTestWorld handle, useSandbox for contract specs and the outage lock; a devDependency of every back end. 1.0.1: world.keycloak.events/sessions (the user events of the realm and live sessions through the admin API) and world.infra.postgresql.connection(name) (the database of one connection down while the others serve, under the outage lock).'
|
|
78
78
|
'@starci/cli':
|
|
79
|
-
version: 1.0.
|
|
79
|
+
version: 1.0.1
|
|
80
80
|
group: starci
|
|
81
81
|
install: registry
|
|
82
82
|
side: both
|
|
83
83
|
source: packages/cli/package.json
|
|
84
84
|
why: '1.0.0: the only StarCi command package; product repositories install it for starci app, while @starci/hfs remains its pinned transitive implementation.'
|
|
85
85
|
'@starci/hfs':
|
|
86
|
-
version: 4.0.
|
|
86
|
+
version: 4.0.10
|
|
87
87
|
group: starci
|
|
88
88
|
install: registry
|
|
89
89
|
side: both
|
|
@@ -346,7 +346,7 @@ rules:
|
|
|
346
346
|
- {kind: eslint-be, id: entrypoint-only-in-apps}
|
|
347
347
|
- id: R34
|
|
348
348
|
code: "BE_SCHEMA_AUTHORITY"
|
|
349
|
-
law: "Migrations
|
|
349
|
+
law: "Migrations own the application schema and run only through cli migrate or TestWorld; `synchronize` is `false`. TestWorld infrastructure may provision generated write-fault triggers and functions only in its verified private connection, preserving production constraints and existing triggers, holding its operation lock through catalog-confirmed cleanup."
|
|
350
350
|
kinds: [lint, codemod, design]
|
|
351
351
|
gates: [pre-commit, pre-push, settle, land, ci]
|
|
352
352
|
failureCodes: ["BE_SCHEMA_AUTHORITY"]
|
|
@@ -1253,7 +1253,7 @@ rules:
|
|
|
1253
1253
|
- {kind: runtime, id: generated-untracked, at: scripts/hfs/runtime-rules/generated-untracked.mjs}
|
|
1254
1254
|
- id: R122
|
|
1255
1255
|
code: "RT_CITED_PATH_MISSING"
|
|
1256
|
-
law: "A runtime path cited by live prose or contracts (modules
|
|
1256
|
+
law: "A runtime path cited by live prose or contracts (modules, docs, skills, knowledge, CONTEXT.md, README.md, CONTRIBUTING.md, init, the ui docs) exists; a retired or moved path is cited only by history."
|
|
1257
1257
|
scope: runtime
|
|
1258
1258
|
kinds: [check]
|
|
1259
1259
|
gates: [land, runtime]
|
|
@@ -1736,7 +1736,7 @@ rules:
|
|
|
1736
1736
|
- {kind: runtime, id: facts, at: scripts/hfs/runtime-rules/facts.mjs}
|
|
1737
1737
|
- id: R193
|
|
1738
1738
|
code: "RT_RULE_ID_UNKNOWN"
|
|
1739
|
-
law: "Every rule id (R<digits>) that tracked knowledge, docs, code or data names is a rule of this catalog, and the ids run from R01 to the last rule with no undeclared gap: an id with no rule is listed under `retired` with its reason (RT_RULE_ID_GAP), and every rule of the catalog is named by the `hfsRules:` of at least one knowledge/patterns topic or carries `scope: runtime` (RT_RULE_UNCITED). A retired id is a history name that only
|
|
1739
|
+
law: "Every rule id (R<digits>) that tracked knowledge, docs, code or data names is a rule of this catalog, and the ids run from R01 to the last rule with no undeclared gap: an id with no rule is listed under `retired` with its reason (RT_RULE_ID_GAP), and every rule of the catalog is named by the `hfsRules:` of at least one knowledge/patterns topic or carries `scope: runtime` (RT_RULE_UNCITED). A retired id is a history name that only changelogs may still spell; specs and generated copies are not read."
|
|
1740
1740
|
scope: runtime
|
|
1741
1741
|
kinds: [check]
|
|
1742
1742
|
gates: [land, runtime]
|
|
@@ -1772,7 +1772,7 @@ rules:
|
|
|
1772
1772
|
- {kind: runtime, id: prose-restates, at: scripts/hfs/runtime-rules/prose-restates.mjs}
|
|
1773
1773
|
- id: R197
|
|
1774
1774
|
code: "RT_RETIRED_CLI_CALL"
|
|
1775
|
-
law: "No tracked file invokes a command the unified CLI removed: the retired kernel, hfs, test-stack and top-level runtime spellings listed in the `removed` fields of the catalog and in the dispatcher table; those fields
|
|
1775
|
+
law: "No tracked file invokes a command the unified CLI removed: the retired kernel, hfs, test-stack and top-level runtime spellings listed in the `removed` fields of the catalog and in the dispatcher table; those fields and changelogs are exempt."
|
|
1776
1776
|
scope: runtime
|
|
1777
1777
|
kinds: [check]
|
|
1778
1778
|
gates: [land, runtime]
|
|
@@ -1851,7 +1851,7 @@ rules:
|
|
|
1851
1851
|
- {kind: runtime, id: doc-owner, at: scripts/checks/check-doc-owner.mjs}
|
|
1852
1852
|
- id: R206
|
|
1853
1853
|
code: "RT_EXAMPLE_COUPLING"
|
|
1854
|
-
law: "Runtime source never couples to a product or its example: no tracked file under scripts/, engine/, packages/, ui/src or ui/api - and no managed template under packages/hfs/templates/ - spells a literal examples/<name>, one of the product names this runtime shipped against (the closed PRODUCT_NAMES list of scripts/lib/example-refs.mjs), an inc-<hash> a product name prefixes, or a host drive path. Specs, tests, the generated runtime copies,
|
|
1854
|
+
law: "Runtime source never couples to a product or its example: no tracked file under scripts/, engine/, packages/, ui/src or ui/api - and no managed template under packages/hfs/templates/ - spells a literal examples/<name>, one of the product names this runtime shipped against (the closed PRODUCT_NAMES list of scripts/lib/example-refs.mjs), an inc-<hash> a product name prefixes, or a host drive path. Specs, tests, the generated runtime copies, changelogs and .starciwork records are out of scope; a read the runtime genuinely makes at run time is declared once in scripts/lib/example-refs.mjs and every consumer names that constant."
|
|
1855
1855
|
scope: runtime
|
|
1856
1856
|
kinds: [check]
|
|
1857
1857
|
gates: [land, runtime]
|
|
@@ -1860,7 +1860,7 @@ rules:
|
|
|
1860
1860
|
- {kind: runtime, id: example-coupling, at: scripts/checks/check-example-coupling.mjs}
|
|
1861
1861
|
- id: R207
|
|
1862
1862
|
code: "RT_RETIRED_NAME_LIVE"
|
|
1863
|
-
law: "A name the retired registry declares dead never appears in a live tracked file — not in prose, a comment, a string literal or a path: every retired[].path and moved[].from of modules/kernel/retired-paths.yaml, and every retiredNames[] naming that is not a path (a deleted app, a layer naming, a verb prefix). History (
|
|
1863
|
+
law: "A name the retired registry declares dead never appears in a live tracked file — not in prose, a comment, a string literal or a path: every retired[].path and moved[].from of modules/kernel/retired-paths.yaml, and every retiredNames[] naming that is not a path (a deleted app, a layer naming, a verb prefix). History (a changelog, a benchmark finding, a .starciwork record), the registry itself and the files of the check may name what was deleted; a slot manifest's forbids value or forbidden-presence tombstone declares a refusal, not a use."
|
|
1864
1864
|
scope: runtime
|
|
1865
1865
|
kinds: [check]
|
|
1866
1866
|
gates: [land, runtime]
|
|
@@ -1869,7 +1869,7 @@ rules:
|
|
|
1869
1869
|
- {kind: runtime, id: retired-names, at: scripts/checks/check-retired-names.mjs}
|
|
1870
1870
|
- id: R208
|
|
1871
1871
|
code: "RT_PORT_RESTATED"
|
|
1872
|
-
law: "Every port literal of the runtime is spelled once, by its owner, and every other file references the owner: the harness UI ports live in modules/models/runtimes.yaml statusApp.port/devPort read through ui/ports.mjs, the local SonarQube host in scripts/gates/sonar-local.mjs DEFAULT_HOST. No in-scope file that is not the owner spells an owned literal in a port position (a scheme://host:NNNN or host:NNNN authority, a *port*/PORT assignment, a listen(NNNN) call), and an unowned literal in a port position of two or more runtime source files gets one owning exported constant the rest reference. Specs, tests, node_modules, the generated runtime copies,
|
|
1872
|
+
law: "Every port literal of the runtime is spelled once, by its owner, and every other file references the owner: the harness UI ports live in modules/models/runtimes.yaml statusApp.port/devPort read through ui/ports.mjs, the local SonarQube host in scripts/gates/sonar-local.mjs DEFAULT_HOST. No in-scope file that is not the owner spells an owned literal in a port position (a scheme://host:NNNN or host:NNNN authority, a *port*/PORT assignment, a listen(NNNN) call), and an unowned literal in a port position of two or more runtime source files gets one owning exported constant the rest reference. Specs, tests, node_modules, the generated runtime copies, changelogs, .starciwork and the product stack declarations (.starcistacks, starcistacks-services - a product owns the ports of the services it runs) are out of scope."
|
|
1873
1873
|
scope: runtime
|
|
1874
1874
|
kinds: [check]
|
|
1875
1875
|
gates: [land, runtime]
|
|
@@ -1887,7 +1887,7 @@ rules:
|
|
|
1887
1887
|
- {kind: runtime, id: default-once, at: scripts/checks/check-default-once.mjs}
|
|
1888
1888
|
- id: R210
|
|
1889
1889
|
code: "RT_VERSION_RESTATED"
|
|
1890
|
-
law: "knowledge/hfs/canon-pins.yaml spells the version of every @starci package and every canon-pinned dependency once; a package.json is the declared install site and a lockfile its resolution. Anywhere else a semver literal equal to a pin - or a name@x.y.z specifier naming one - restates it, except on the declared binding keys a refresher owns and rewrites in place (canon.version of the code-pattern bindings held by check-canon-pins.mjs, provenance.version and identity.version of the grammar snapshots rewritten by scripts/work/ui/grammar-knowledge.mjs). Package manifests, lockfiles, changelogs,
|
|
1890
|
+
law: "knowledge/hfs/canon-pins.yaml spells the version of every @starci package and every canon-pinned dependency once; a package.json is the declared install site and a lockfile its resolution. Anywhere else a semver literal equal to a pin - or a name@x.y.z specifier naming one - restates it, except on the declared binding keys a refresher owns and rewrites in place (canon.version of the code-pattern bindings held by check-canon-pins.mjs, provenance.version and identity.version of the grammar snapshots rewritten by scripts/work/ui/grammar-knowledge.mjs). Package manifests, lockfiles, changelogs, specs, tests, the generated runtime copies and .starciwork are out of scope; a restatement is fixed by naming the pin, never the literal."
|
|
1891
1891
|
scope: runtime
|
|
1892
1892
|
kinds: [check]
|
|
1893
1893
|
gates: [land, runtime]
|
|
@@ -1642,6 +1642,46 @@ HFS_LITE_SECRET_CUSTODY:
|
|
|
1642
1642
|
owner: owner
|
|
1643
1643
|
kind: check-finding
|
|
1644
1644
|
|
|
1645
|
+
kernel-caller-stale:
|
|
1646
|
+
title: "Kernel incarnation does not own this mutation"
|
|
1647
|
+
title_vi: "Phiên Kernel không còn sở hữu thao tác"
|
|
1648
|
+
meaning_vi: "Quyền hiện tại đã đổi trước khi ghi dữ liệu hoặc thực hiện tác động."
|
|
1649
|
+
causes_vi:
|
|
1650
|
+
- "Terminal, Dispatch, thế hệ, lượt khởi động hoặc singleton token đã bị thay thế."
|
|
1651
|
+
nextStep_vi: "Giữ nguyên quyền đang tranh chấp; Supervisor kiểm tra phiên hiện tại trước khi thử lại."
|
|
1652
|
+
owner: supervisor
|
|
1653
|
+
kind: verb-refusal
|
|
1654
|
+
|
|
1655
|
+
kernel-caller-unknown:
|
|
1656
|
+
title: "Managed caller custody is unavailable"
|
|
1657
|
+
title_vi: "Không xác minh được quyền của caller"
|
|
1658
|
+
meaning_vi: "Lịch sử hoặc binding của caller không đọc được hoặc mâu thuẫn; không suy ra quyền owner."
|
|
1659
|
+
causes_vi:
|
|
1660
|
+
- "Kho ledger hoặc machine không đọc được; binding sai, cũ hoặc thuộc workflow khác."
|
|
1661
|
+
nextStep_vi: "Khôi phục khả năng đọc và xác minh caller thực tế; không dùng biến môi trường để giả quyền."
|
|
1662
|
+
owner: supervisor
|
|
1663
|
+
kind: verb-refusal
|
|
1664
|
+
|
|
1665
|
+
kernel-caller-actor:
|
|
1666
|
+
title: "Actor claim contradicts native caller"
|
|
1667
|
+
title_vi: "Actor khai báo mâu thuẫn với caller"
|
|
1668
|
+
meaning_vi: "Giá trị --by không khớp với quyền caller mà owner hiện có xác minh."
|
|
1669
|
+
causes_vi:
|
|
1670
|
+
- "Caller dùng tên Kernel, Op hoặc Supervisor khác với phiên thực tế."
|
|
1671
|
+
nextStep_vi: "Bỏ khai báo giả và dùng đúng caller; chuỗi actor không cấp quyền."
|
|
1672
|
+
owner: supervisor
|
|
1673
|
+
kind: verb-refusal
|
|
1674
|
+
|
|
1675
|
+
kernel-read-unverified:
|
|
1676
|
+
title: "Current Kernel READ attestation is incomplete"
|
|
1677
|
+
title_vi: "READ của Kernel hiện tại chưa đầy đủ"
|
|
1678
|
+
meaning_vi: "Manifest READ không chứng minh toàn bộ bytes cần đọc cho phiên Kernel hiện tại."
|
|
1679
|
+
causes_vi:
|
|
1680
|
+
- "File thiếu, hash sai, bytes dirty, descriptor sai hoặc lượt Kernel đã thay đổi."
|
|
1681
|
+
nextStep_vi: "Lấy --plan, đọc đủ từng file và gửi manifest nguyên vẹn với --read-manifest từ Kernel hiện tại."
|
|
1682
|
+
owner: supervisor
|
|
1683
|
+
kind: verb-refusal
|
|
1684
|
+
|
|
1645
1685
|
HFS_MANAGED_FILE_DRIFT:
|
|
1646
1686
|
title: "A managed file equals its template render"
|
|
1647
1687
|
title_vi: "Tệp do .claude quản lý bị lệch"
|
|
@@ -154,9 +154,6 @@ targets:
|
|
|
154
154
|
# difficulty and whose `defaultModel` covers an unpinned difficulty. A pool target names no model itself.
|
|
155
155
|
codex-agent:
|
|
156
156
|
runtime: codex
|
|
157
|
-
profiles:
|
|
158
|
-
working: gpt-6-luna
|
|
159
|
-
reasoning: gpt-6.1-sol
|
|
160
157
|
orcaLaunch: {agent: codex} # orca orchestration worker-start --agent codex
|
|
161
158
|
# Launch-only targets: an explicit `--model gpt-6.1-sol` or `--model gpt-6-luna` request reaches that one model
|
|
162
159
|
# on the Codex provider window. They are off every automatic chain; allocation routes the codex-agent pool.
|
|
@@ -164,16 +161,10 @@ targets:
|
|
|
164
161
|
gpt-6.1-sol:
|
|
165
162
|
runtime: codex
|
|
166
163
|
defaultModel: gpt-6.1-sol
|
|
167
|
-
profiles:
|
|
168
|
-
working: gpt-6.1-sol
|
|
169
|
-
reasoning: gpt-6.1-sol
|
|
170
164
|
orcaLaunch: {agent: codex} # orca orchestration worker-start --agent codex
|
|
171
165
|
gpt-6-luna:
|
|
172
166
|
runtime: codex
|
|
173
167
|
defaultModel: gpt-6-luna
|
|
174
|
-
profiles:
|
|
175
|
-
working: gpt-6-luna
|
|
176
|
-
reasoning: gpt-6-luna
|
|
177
168
|
orcaLaunch: {agent: codex} # orca orchestration worker-start --agent codex
|
|
178
169
|
# Cursor: Orca agent id `cursor` (binary cursor-agent). A launch-only target like gpt-6.1-sol: off every automatic chain,
|
|
179
170
|
# reached by an explicit `--model auto` (the CLI's default) or any id of the account's `cursor-agent --list-models`.
|
|
@@ -183,43 +174,14 @@ targets:
|
|
|
183
174
|
cursor-agent:
|
|
184
175
|
runtime: cursor
|
|
185
176
|
defaultModel: auto
|
|
186
|
-
profiles:
|
|
187
|
-
working: auto
|
|
188
|
-
reasoning: auto
|
|
189
177
|
orcaLaunch: {agent: cursor} # orca orchestration worker-start --agent cursor
|
|
190
178
|
claude-agent:
|
|
191
179
|
runtime: claude
|
|
192
|
-
profiles:
|
|
193
|
-
working: opus
|
|
194
|
-
reasoning: opus-reviewer
|
|
195
180
|
orcaLaunch: {agent: claude} # orca orchestration worker-start --agent claude
|
|
196
181
|
devin-agent:
|
|
197
182
|
runtime: devin
|
|
198
183
|
executionHosts: [orca]
|
|
199
|
-
profiles:
|
|
200
|
-
working: devin-worker
|
|
201
|
-
reasoning: devin-reviewer
|
|
202
184
|
orcaLaunch: {agent: devin} # orca orchestration worker-start --agent devin
|
|
203
|
-
targetAliases:
|
|
204
|
-
codex-gpt-6.1-sol: codex-agent
|
|
205
|
-
codex-gpt-6-luna: codex-agent
|
|
206
|
-
devin-devin-worker: devin-agent
|
|
207
|
-
devin-devin-reviewer: devin-agent
|
|
208
|
-
# Bare model ids that are not targets resolve to the provider-window pool that carries them, so workflow
|
|
209
|
-
# quota entries land on the right window. gpt-6.1-sol and gpt-6-luna are targets; their `runtime: codex`
|
|
210
|
-
# names the window.
|
|
211
|
-
claude-opus: claude-agent
|
|
212
|
-
claude-opus-5-5: claude-agent
|
|
213
|
-
skills:
|
|
214
|
-
starci:
|
|
215
|
-
chains:
|
|
216
|
-
working:
|
|
217
|
-
- devin-agent
|
|
218
|
-
- codex-agent
|
|
219
|
-
- claude-agent
|
|
220
|
-
reasoning:
|
|
221
|
-
- claude-agent
|
|
222
|
-
- codex-agent
|
|
223
185
|
# An operator whose kind runtimes.yaml roleOfKind marks think work with no declared order carries the
|
|
224
186
|
# think order of runtimes.yaml allocation.preference.think (Opus, then Sol) and nothing else;
|
|
225
187
|
# scripts/route/route-model.mjs refuses any other pool for it. A kind's declared order wins: owner routing
|
|
@@ -524,7 +486,7 @@ selection: |-
|
|
|
524
486
|
operator chain from top to bottom, choosing the first ready runtime/profile. Fall through only
|
|
525
487
|
after an allowed failure with verified no-effect state. This policy never installs an agent,
|
|
526
488
|
authenticates a provider, grants tools or expands the workflow authority. Every pool launches the one
|
|
527
|
-
way (modules/kernel/
|
|
489
|
+
way (modules/kernel/start-workflow.yaml): `orca orchestration worker-start --agent
|
|
528
490
|
<runtime> [--model <id> --effort <level>] --spec <prompt>` (Orca files the Task), the unattended flags from Orca's per-agent
|
|
529
491
|
default args; the launcher takes the agent terminal from the start receipt (else worker-show) and attests the effective agent and model
|
|
530
492
|
(worker-show). Devin takes no model flag (Orca: --model is for Claude, Codex and Cursor) and starts on its CLI default.
|
|
@@ -275,9 +275,6 @@ allocation:
|
|
|
275
275
|
# start waits for an Orca that does not answer, and how long after a responding Orca calls a kernel dead
|
|
276
276
|
# it is asked again.
|
|
277
277
|
hostOutage: {waitMs: 90000, deathSettleMs: 10000}
|
|
278
|
-
# resume: unused since resume-all.mjs was retired (2026-09-28); the logon run waited orcaWaitMs for Orca; the scheduled task
|
|
279
|
-
# re-runs resume every everyMs (whole minutes: schtasks /MO).
|
|
280
|
-
resume: {orcaWaitMs: 600000, everyMs: 600000}
|
|
281
278
|
# supervisorTick: the numbers of the retired supervisor tick (2026-09-28) that the reconciler controllers still read
|
|
282
279
|
# (host thresholds, orca probes, statusApp port, owner digest cadence); everyMs is unused. The tick ran every
|
|
283
280
|
# everyMs (whole minutes: schtasks repetition). host: above maxNode node.exe or maxGit git.exe processes the host
|
|
@@ -354,9 +351,6 @@ allocation:
|
|
|
354
351
|
routeAs: implementation.plan
|
|
355
352
|
minDifficulty: hard
|
|
356
353
|
effort: high
|
|
357
|
-
# restart: unused since restart-all.mjs was retired (2026-09-28); it waited waitMs for every running workflow to show one live
|
|
358
|
-
# Kernel, polling every pollMs.
|
|
359
|
-
restart: {waitMs: 480000, pollMs: 20000}
|
|
360
354
|
# selfReload (scripts/machine/self-reload.mjs): a watchdog loop re-execs at most once per minIntervalMs; its
|
|
361
355
|
# replacement takes over the loop's lock within handoverMs or is stopped.
|
|
362
356
|
selfReload: {minIntervalMs: 300000, handoverMs: 30000}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# _labels.yaml — the human name of every operation, one shared map.
|
|
2
|
+
# The underscore prefix keeps this file out of the op catalog scan, like
|
|
3
|
+
# _common.yaml: scripts/route/build-ops-registry.mjs and route-op.mjs skip
|
|
4
|
+
# modules/ops/*.yaml files whose names start with "_".
|
|
5
|
+
#
|
|
6
|
+
# Owner request 2026-09-27: kernels, workflows and op workers carry names a person understands. The
|
|
7
|
+
# op id (interface.draw) stays the key everywhere — the ledger, the api, job ids, contracts; this map
|
|
8
|
+
# only says what a human reads for it. Every reader goes through scripts/lib/display-names.mjs
|
|
9
|
+
# (opLabel): Orca [Op] worker titles, starci kernel status legs/nextActions `label`, the supervisor digest and
|
|
10
|
+
# progress report, Telegram notices and the harness UI (ui/server.mjs sends the map in the snapshot
|
|
11
|
+
# as `opLabels`, so the UI shows exactly these words). tests/lib/display-names.spec.mjs keeps one entry per
|
|
12
|
+
# modules/ops/ops/*.yaml. `vi` is what the owner reads; `en` is the UI's English rendering of it.
|
|
13
|
+
schema: starci/op-labels@1
|
|
14
|
+
|
|
15
|
+
labels:
|
|
16
|
+
request.analyze: { vi: Phân tích yêu cầu, en: Analyze request }
|
|
17
|
+
task.execute: { vi: Chọn luồng việc, en: Select workflow }
|
|
18
|
+
scope.define: { vi: Xác định phạm vi, en: Define scope }
|
|
19
|
+
scope.finish: { vi: Kết thúc phạm vi, en: Finish scope }
|
|
20
|
+
goal.revise: { vi: Sửa mục tiêu, en: Revise goal }
|
|
21
|
+
business.decide: { vi: Chốt nghiệp vụ, en: Decide business rules }
|
|
22
|
+
architecture.decide: { vi: Thiết kế kiến trúc, en: Design architecture }
|
|
23
|
+
brand.decide: { vi: Chốt thương hiệu, en: Decide brand }
|
|
24
|
+
decision.prepare: { vi: Chuẩn bị quyết định, en: Prepare decision }
|
|
25
|
+
provision.ask: { vi: Xin thông tin, en: Request information }
|
|
26
|
+
work.author: { vi: Chia việc chi tiết, en: Plan work }
|
|
27
|
+
workspace.manage: { vi: Dựng workspace, en: Manage workspace }
|
|
28
|
+
knowledge.repair: { vi: Sửa tri thức, en: Repair knowledge }
|
|
29
|
+
grammar.update: { vi: Bổ sung Grammar, en: Update grammar }
|
|
30
|
+
interface.draw: { vi: Vẽ giao diện, en: Draw interface }
|
|
31
|
+
interface.asset: { vi: Làm hình ảnh, en: Make assets }
|
|
32
|
+
interface.scaffold: { vi: Dựng khung giao diện, en: Scaffold interface }
|
|
33
|
+
interface.implement: { vi: Code giao diện, en: Implement interface }
|
|
34
|
+
interface.audit: { vi: Soát giao diện, en: Audit interface }
|
|
35
|
+
backend.scaffold: { vi: Dựng khung backend, en: Scaffold backend }
|
|
36
|
+
backend.implement: { vi: Code backend, en: Implement backend }
|
|
37
|
+
package.scaffold: { vi: Dựng khung package, en: Scaffold package }
|
|
38
|
+
code.refactor: { vi: Chỉnh sửa mã nguồn, en: Refactor code }
|
|
39
|
+
content.generate: { vi: Soạn nội dung, en: Generate content }
|
|
40
|
+
docs.author: { vi: Viết tài liệu, en: Write docs }
|
|
41
|
+
test.author: { vi: Viết test, en: Write tests }
|
|
42
|
+
integration.verify: { vi: Kiểm thử tích hợp, en: Integration test }
|
|
43
|
+
e2e.verify: { vi: Kiểm thử đầu cuối, en: End-to-end test }
|
|
44
|
+
perf.verify: { vi: Kiểm thử hiệu năng, en: Performance test }
|
|
45
|
+
unit.verify: { vi: Chạy toàn bộ unit test, en: Full unit test run }
|
|
46
|
+
security.verify: { vi: Soát bảo mật, en: Security review }
|
|
47
|
+
uat.verify: { vi: Kiểm thử UAT, en: UAT test }
|
|
48
|
+
uat.assisted.prepare: { vi: Chuẩn bị UAT có người, en: Prepare assisted UAT }
|
|
49
|
+
uat.assisted.verify: { vi: Xác minh UAT có người, en: Verify assisted UAT }
|
|
50
|
+
review.verify: { vi: Review cuối, en: Final review }
|
|
51
|
+
handover.review: { vi: Bàn giao, en: Handover }
|
|
52
|
+
release.deliver: { vi: Phát hành, en: Release }
|
|
53
|
+
runtime.operate: { vi: Vận hành môi trường, en: Operate runtime }
|