wicked-crew 0.7.33 → 0.7.35
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/dist/api/chat-scope.d.ts +4 -0
- package/dist/api/chat-scope.d.ts.map +1 -1
- package/dist/api/chat-scope.js +16 -10
- package/dist/api/chat-scope.js.map +1 -1
- package/dist/api/chat-transcripts.d.ts +60 -0
- package/dist/api/chat-transcripts.d.ts.map +1 -0
- package/dist/api/chat-transcripts.js +139 -0
- package/dist/api/chat-transcripts.js.map +1 -0
- package/dist/api/chat-turns.d.ts +43 -4
- package/dist/api/chat-turns.d.ts.map +1 -1
- package/dist/api/chat-turns.js +73 -6
- package/dist/api/chat-turns.js.map +1 -1
- package/dist/api/eval-sample.d.ts +18 -18
- package/dist/api/governance-steering.d.ts +4 -4
- package/dist/api/requirements.d.ts +3 -5
- package/dist/api/requirements.d.ts.map +1 -1
- package/dist/api/requirements.js +30 -146
- package/dist/api/requirements.js.map +1 -1
- package/dist/api/routes.d.ts +19 -8
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +144 -9
- package/dist/api/routes.js.map +1 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +60 -1
- package/dist/api/server.js.map +1 -1
- package/dist/api/skills.d.ts.map +1 -1
- package/dist/api/skills.js +16 -4
- package/dist/api/skills.js.map +1 -1
- package/dist/campaigns/routes.d.ts +14 -14
- package/dist/cli/index.js +81 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/core/adapter.d.ts +27 -0
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +98 -7
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/engine-roster.d.ts +28 -0
- package/dist/core/engine-roster.d.ts.map +1 -1
- package/dist/core/engine-roster.js +28 -0
- package/dist/core/engine-roster.js.map +1 -1
- package/dist/core/types.d.ts +10 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +8 -0
- package/dist/core/types.js.map +1 -1
- package/dist/core/versions.d.ts +17 -0
- package/dist/core/versions.d.ts.map +1 -0
- package/dist/core/versions.js +35 -0
- package/dist/core/versions.js.map +1 -0
- package/dist/interactive/bridge-pool.d.ts +18 -9
- package/dist/interactive/bridge-pool.d.ts.map +1 -1
- package/dist/interactive/bridge-pool.js +30 -9
- package/dist/interactive/bridge-pool.js.map +1 -1
- package/dist/interactive/bridge-root.d.ts +47 -16
- package/dist/interactive/bridge-root.d.ts.map +1 -1
- package/dist/interactive/bridge-root.js +102 -31
- package/dist/interactive/bridge-root.js.map +1 -1
- package/dist/interactive/doc-delete-routes.d.ts +3 -1
- package/dist/interactive/doc-delete-routes.d.ts.map +1 -1
- package/dist/interactive/doc-delete-routes.js.map +1 -1
- package/dist/interactive/doc-list-routes.d.ts +3 -1
- package/dist/interactive/doc-list-routes.d.ts.map +1 -1
- package/dist/interactive/doc-list-routes.js.map +1 -1
- package/dist/interactive/docs-index.d.ts +3 -1
- package/dist/interactive/docs-index.d.ts.map +1 -1
- package/dist/interactive/docs-index.js +1 -1
- package/dist/interactive/docs-index.js.map +1 -1
- package/dist/interactive/project-root.d.ts +8 -2
- package/dist/interactive/project-root.d.ts.map +1 -1
- package/dist/interactive/project-root.js +1 -1
- package/dist/interactive/project-root.js.map +1 -1
- package/dist/interactive/proxy-routes.d.ts +3 -1
- package/dist/interactive/proxy-routes.d.ts.map +1 -1
- package/dist/interactive/proxy-routes.js.map +1 -1
- package/dist/projects/state-home-preflight.d.ts +186 -0
- package/dist/projects/state-home-preflight.d.ts.map +1 -0
- package/dist/projects/state-home-preflight.js +353 -0
- package/dist/projects/state-home-preflight.js.map +1 -0
- package/dist/projects/state-home-registry.d.ts +25 -0
- package/dist/projects/state-home-registry.d.ts.map +1 -0
- package/dist/projects/state-home-registry.js +52 -0
- package/dist/projects/state-home-registry.js.map +1 -0
- package/dist/qe/acceptance.d.ts +9 -0
- package/dist/qe/acceptance.d.ts.map +1 -1
- package/dist/qe/acceptance.js +9 -0
- package/dist/qe/acceptance.js.map +1 -1
- package/dist/qe/author-workflow.d.ts.map +1 -1
- package/dist/qe/author-workflow.js +2 -1
- package/dist/qe/author-workflow.js.map +1 -1
- package/dist/skills/base-skill.d.ts +104 -0
- package/dist/skills/base-skill.d.ts.map +1 -0
- package/dist/skills/base-skill.js +102 -0
- package/dist/skills/base-skill.js.map +1 -0
- package/dist/skills/guards.d.ts.map +1 -1
- package/dist/skills/guards.js +3 -0
- package/dist/skills/guards.js.map +1 -1
- package/dist/skills/runtime.d.ts +44 -5
- package/dist/skills/runtime.d.ts.map +1 -1
- package/dist/skills/runtime.js +103 -11
- package/dist/skills/runtime.js.map +1 -1
- package/dist/skills/store.d.ts +41 -0
- package/dist/skills/store.d.ts.map +1 -1
- package/dist/skills/store.js +175 -3
- package/dist/skills/store.js.map +1 -1
- package/dist/skills/tree.d.ts +12 -0
- package/dist/skills/tree.d.ts.map +1 -1
- package/dist/skills/tree.js +26 -0
- package/dist/skills/tree.js.map +1 -1
- package/dist/studio/assets/{index-DhojBZ1T.js → index-CJp35PKj.js} +99 -98
- package/dist/studio/index.html +1 -1
- package/dist/studio/testid-inventory.json +63 -3
- package/endpoint-manifest.json +2 -1
- package/package.json +4 -4
package/dist/api/routes.js
CHANGED
|
@@ -5,7 +5,7 @@ import { readFileSync, existsSync } from 'node:fs';
|
|
|
5
5
|
import { promises as fsp } from 'node:fs';
|
|
6
6
|
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
import { CampaignsUnsupportedError, ChatUnsupportedError, CoreAdapter, ElicitationUnsupportedError, SteeringUnsupportedError, humanGatePhaseIds } from '../core/adapter.js';
|
|
8
|
+
import { CampaignsUnsupportedError, ChatUnsupportedError, CoreAdapter, ElicitationUnsupportedError, SteeringUnsupportedError, humanGatePhaseIds, settingsFilePath } from '../core/adapter.js';
|
|
9
9
|
import { codeGraphDb, codeGraphErrorStatus, requirementsGraph } from '../core/repoPaths.js';
|
|
10
10
|
import { resolveCursorUnit } from '../core/cursor.js';
|
|
11
11
|
import { detectRefusal } from './gate-cache.js';
|
|
@@ -35,6 +35,9 @@ import { isSteeringAuthorRun, landSteeringProposal } from './steering-landing.js
|
|
|
35
35
|
import { registerTestingRoutes } from './testing.js';
|
|
36
36
|
import { registerSkillsRoutes } from './skills.js';
|
|
37
37
|
import { disabledSkillsHealth } from '../skills/runtime.js';
|
|
38
|
+
import { STATE_HOME_BLOCKER_CODE, STATE_HOME_REMEDY, isEngineStateHomeRefusal, stateHomeBlockerBody, } from '../projects/state-home-preflight.js';
|
|
39
|
+
import { BASE_SKILL_POLICIES, BASE_SKILL_REF_SHAPE } from '../skills/base-skill.js';
|
|
40
|
+
import { noEligibleSeatBody, parseNoEligibleSeat } from '../core/engine-roster.js';
|
|
38
41
|
import { ProjectSettingsStore } from '../projects/settings.js';
|
|
39
42
|
import { boundOrigin, InteractiveBridgePool } from '../interactive/bridge-pool.js';
|
|
40
43
|
import { composeDeliverText, factsFromRun, framedDeliverText, runUrlFor } from '../core/deliver-text.js';
|
|
@@ -486,6 +489,7 @@ runtime = {}) {
|
|
|
486
489
|
const guidanceIndex = runtime.guidanceIndex ?? new GuidanceIndex();
|
|
487
490
|
const chatScopes = runtime.chatScopes ?? new ChatScopeIndex();
|
|
488
491
|
const chatTurns = runtime.chatTurns ?? new ChatTurnIndex();
|
|
492
|
+
const chatTranscripts = runtime.chatTranscripts;
|
|
489
493
|
const deliveryIndex = runtime.deliveryIndex ?? new DeliveryIndex();
|
|
490
494
|
// Wave 6: the doc↔run binding and the registered test sets — `createServer` injects the real
|
|
491
495
|
// ones; a directly-driven route set gets an empty index (every run `document_id: null`, no sets).
|
|
@@ -632,7 +636,26 @@ runtime = {}) {
|
|
|
632
636
|
const capabilities = typeof adapter.engineCapabilities === 'function'
|
|
633
637
|
? adapter.engineCapabilities()
|
|
634
638
|
: { deliverGate: false };
|
|
635
|
-
|
|
639
|
+
// wicked-core#411 / crew#497: the state-home blocker rides the health probe as a WARNING. The
|
|
640
|
+
// daemon still SERVES (status stays ok — studio must load and show the blocker) but refuses to
|
|
641
|
+
// launch while the state home holds an entry the worker Read fence cannot classify. Re-surveyed
|
|
642
|
+
// per probe (two readdirs) so an entry that appears after boot is reported without a restart;
|
|
643
|
+
// the field is ABSENT when there is nothing to say, and on a route set booted without the watch.
|
|
644
|
+
const stateHome = runtime.stateHome !== undefined ? await runtime.stateHome.refresh() : null;
|
|
645
|
+
const warnings = stateHome === null
|
|
646
|
+
? []
|
|
647
|
+
: stateHome.findings.map((f) => ({ kind: f.kind, severity: f.severity, message: f.message }));
|
|
648
|
+
// crew#554: the BASE skill posture for the next launch — the composer's confirm line
|
|
649
|
+
// ("discipline skill: <name> gen N" / "MISSING — …"). Cached by the skills runtime: no I/O.
|
|
650
|
+
const baseSkill = runtime.skills?.baseSkill() ?? null;
|
|
651
|
+
return {
|
|
652
|
+
status: 'ok',
|
|
653
|
+
version: PKG_VERSION,
|
|
654
|
+
ping,
|
|
655
|
+
capabilities,
|
|
656
|
+
baseSkill,
|
|
657
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
658
|
+
};
|
|
636
659
|
});
|
|
637
660
|
// The daemon's self-knowledge surface (diagnostics): what is deployed, what it stores, what
|
|
638
661
|
// has gone wrong recently, and whether ACP is really working across the CLIs — the answer
|
|
@@ -680,6 +703,11 @@ runtime = {}) {
|
|
|
680
703
|
// Is the governance evidence LANDING (crew#495): the store, the records on it, the dead
|
|
681
704
|
// letters — with a `governance.deadletter` finding the moment the outbox holds one.
|
|
682
705
|
governance,
|
|
706
|
+
// The state-home classification (wicked-core#411 / crew#497): which state home the worker
|
|
707
|
+
// Read fence classifies, every entry it cannot classify there (top level and skills root),
|
|
708
|
+
// who classified (the engine, or crew's registry copy on an older addon) and whether that
|
|
709
|
+
// refuses launches — re-surveyed per read. `null` on a route set booted without the watch.
|
|
710
|
+
stateHome: runtime.stateHome !== undefined ? await runtime.stateHome.refresh() : null,
|
|
683
711
|
};
|
|
684
712
|
});
|
|
685
713
|
// Who am I talking to the daemon as? (task #88). In local mode this is
|
|
@@ -1025,10 +1053,13 @@ runtime = {}) {
|
|
|
1025
1053
|
requestType: 'LaunchRunBody',
|
|
1026
1054
|
responseType: '{ runId: string }',
|
|
1027
1055
|
// 404/409: unknown project / unknown campaignId (wicked-studio#27) /
|
|
1028
|
-
// archived-or-synthesized project + busy engine
|
|
1029
|
-
//
|
|
1030
|
-
//
|
|
1031
|
-
|
|
1056
|
+
// archived-or-synthesized project + busy engine + the state-home blocker
|
|
1057
|
+
// (`state_home_unregistered`) + a roster with no eligible seat (`no_eligible_seat`,
|
|
1058
|
+
// wicked-core#461 — see the catch below); 400: zod reject or a retryOf naming no
|
|
1059
|
+
// existing run; 422: the base-skill intake refusal (`base_skill_refused`, crew#554);
|
|
1060
|
+
// 501: campaignId attach on an engine addon without the campaign bindings ("upgrade
|
|
1061
|
+
// the engine").
|
|
1062
|
+
statusCodes: [201, 400, 404, 409, 422, 501],
|
|
1032
1063
|
},
|
|
1033
1064
|
},
|
|
1034
1065
|
}, async (req, reply) => {
|
|
@@ -1037,6 +1068,18 @@ runtime = {}) {
|
|
|
1037
1068
|
return reply.code(400).send(invalidBody(parsed.error, 'Invalid request body'));
|
|
1038
1069
|
}
|
|
1039
1070
|
const b = parsed.data;
|
|
1071
|
+
// wicked-core#411 / crew#497: the state-home blocker, judged BEFORE anything is resolved or
|
|
1072
|
+
// committed. While the handed skills snapshot derives a state home with an entry the worker
|
|
1073
|
+
// Read fence cannot classify, the engine refuses this launch at intake (and an engine before
|
|
1074
|
+
// that fix refused it at the run's first worker, after councils and the intake gate) — so the
|
|
1075
|
+
// daemon answers the typed 409 itself, naming every entry and the remedy, until they are gone.
|
|
1076
|
+
// Re-surveyed per launch: the entries named are the ones there NOW.
|
|
1077
|
+
if (runtime.stateHome !== undefined) {
|
|
1078
|
+
const stateHome = await runtime.stateHome.refresh();
|
|
1079
|
+
if (stateHome.refusesLaunches) {
|
|
1080
|
+
return reply.code(409).send(stateHomeBlockerBody(stateHome));
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1040
1083
|
const input = {
|
|
1041
1084
|
problem: b.problem,
|
|
1042
1085
|
sessionId: b.sessionId ?? randomUUID(),
|
|
@@ -1195,6 +1238,34 @@ runtime = {}) {
|
|
|
1195
1238
|
}
|
|
1196
1239
|
catch (err) {
|
|
1197
1240
|
const msg = message(err);
|
|
1241
|
+
// The engine's own intake refusal (wicked-core#411 `StateHomeConfigError`) is a configuration
|
|
1242
|
+
// error, not a malformed request: the same typed 409 the pre-check above answers, over a fresh
|
|
1243
|
+
// survey — the pre-check saw a clean state home (or an addon that could not classify), the
|
|
1244
|
+
// engine did not.
|
|
1245
|
+
if (isEngineStateHomeRefusal(msg)) {
|
|
1246
|
+
const stateHome = runtime.stateHome !== undefined ? await runtime.stateHome.refresh() : null;
|
|
1247
|
+
return reply
|
|
1248
|
+
.code(409)
|
|
1249
|
+
.send(stateHome !== null && stateHome.unregistered.length > 0
|
|
1250
|
+
? stateHomeBlockerBody(stateHome)
|
|
1251
|
+
: {
|
|
1252
|
+
error: msg,
|
|
1253
|
+
code: STATE_HOME_BLOCKER_CODE,
|
|
1254
|
+
stateHome: stateHome?.stateHome ?? null,
|
|
1255
|
+
unregistered: [],
|
|
1256
|
+
remedy: STATE_HOME_REMEDY,
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
// wicked-core#461 / crew#556: the engine's typed `NoEligibleSeat` intake refusal — the plan
|
|
1260
|
+
// needs a seat and every seat of the roster was benched by the launcher (signed out / quota /
|
|
1261
|
+
// not installed). A 409 with a typed body, not the 400 the generic arm below answered: the
|
|
1262
|
+
// request is well-formed and succeeds unchanged once a seat is signed in — the conflict is
|
|
1263
|
+
// with the roster's standing (the `state_home_unregistered` rule) — and not a 5xx: the engine
|
|
1264
|
+
// is healthy and said no, naming every seat and its cause.
|
|
1265
|
+
const noSeat = parseNoEligibleSeat(msg);
|
|
1266
|
+
if (noSeat !== null) {
|
|
1267
|
+
return reply.code(409).send(noEligibleSeatBody(msg, noSeat));
|
|
1268
|
+
}
|
|
1198
1269
|
// An unknown/archived project is a state conflict on a real resource, not a malformed
|
|
1199
1270
|
// request: 404/409 per the projects error mapping; anything else keeps the launch 400/409.
|
|
1200
1271
|
if (b.projectId !== undefined && /project.*not registered/i.test(msg)) {
|
|
@@ -1203,6 +1274,17 @@ runtime = {}) {
|
|
|
1203
1274
|
if (b.projectId !== undefined && /archived|'default'|synthesized/i.test(msg)) {
|
|
1204
1275
|
return reply.code(409).send({ error: msg });
|
|
1205
1276
|
}
|
|
1277
|
+
// crew#554 / wicked-core#468: the engine refused the launch AT INTAKE because the handed
|
|
1278
|
+
// skills snapshot lacks the run's BASE skill (`SkillsError::BaseSkillRefused` — the message
|
|
1279
|
+
// names the skill and "refused at intake"). Nothing was planned or persisted. A typed 422 so a
|
|
1280
|
+
// composer renders a clear card (skill, policy, remedy) instead of a generic launch 400.
|
|
1281
|
+
if (/\bbase skill\b.*\brefused at intake\b/is.test(msg)) {
|
|
1282
|
+
const posture = runtime.skills?.baseSkill() ?? null;
|
|
1283
|
+
const remedy = posture !== null && posture.inCatalog
|
|
1284
|
+
? `the skill is in the catalog — POST /skills/publish hands it to the next launch (or set baseSkillRef "" via PUT /settings to turn the base skill off explicitly)`
|
|
1285
|
+
: `install a wicked-garden that ships the skill, POST /skills/refresh-baseline, then POST /skills/publish (or set baseSkillRef "" via PUT /settings to turn the base skill off explicitly)`;
|
|
1286
|
+
return reply.code(422).send({ code: 'base_skill_refused', error: msg, baseSkill: posture, remedy });
|
|
1287
|
+
}
|
|
1206
1288
|
const busy = /busy|in flight|already/i.test(msg);
|
|
1207
1289
|
return reply.code(busy ? 409 : 400).send({ error: msg });
|
|
1208
1290
|
}
|
|
@@ -1863,7 +1945,15 @@ runtime = {}) {
|
|
|
1863
1945
|
if (!parsed.success) {
|
|
1864
1946
|
return reply.code(400).send(invalidBody(parsed.error, 'Invalid request body'));
|
|
1865
1947
|
}
|
|
1866
|
-
|
|
1948
|
+
// DES-L5 §5-c (F-E2E-041): the audience is decided HERE — the named targets, else the engine's
|
|
1949
|
+
// warm seats — so the turn can be opened BEFORE the engine call, in the same tick as the
|
|
1950
|
+
// predicate below: two racing sends can no longer both pass `inFlight` during the await, and a
|
|
1951
|
+
// `chatDelta` that lands before `chatSend` resolves is already stamped with its `turn_id`.
|
|
1952
|
+
const audience = parsed.data.targets ?? (await adapter.chatSeats(id));
|
|
1953
|
+
if (audience.length === 0) {
|
|
1954
|
+
return reply.code(409).send({ error: `chat '${id}' has no warm seats — open it first` });
|
|
1955
|
+
}
|
|
1956
|
+
const inFlight = chatTurns.inFlight(id, audience);
|
|
1867
1957
|
if (inFlight !== null) {
|
|
1868
1958
|
const ageS = Math.round(inFlight.ageMs / 1000);
|
|
1869
1959
|
return reply.code(409).send({
|
|
@@ -1875,12 +1965,22 @@ runtime = {}) {
|
|
|
1875
1965
|
turn: inFlight,
|
|
1876
1966
|
});
|
|
1877
1967
|
}
|
|
1968
|
+
// Sync, same tick as `inFlight`: the window is closed before anything yields.
|
|
1969
|
+
const turn = chatTurns.begin(id, audience, parsed.data.text);
|
|
1878
1970
|
try {
|
|
1879
1971
|
const seats = await adapter.chatSend(id, parsed.data.text, parsed.data.targets);
|
|
1880
|
-
|
|
1972
|
+
if (turn !== null) {
|
|
1973
|
+
// The engine's answer is the truth: the reserved audience is squared with the seats it
|
|
1974
|
+
// reached, and the operator's message joins the transcript with exactly those seats.
|
|
1975
|
+
chatTurns.reconcile(id, turn.turnId, seats);
|
|
1976
|
+
chatTranscripts?.appendUser(id, turn.turnId, parsed.data.text, seats);
|
|
1977
|
+
}
|
|
1881
1978
|
return reply.code(202).send({ seats, ...(turn !== null ? { turnId: turn.turnId } : {}) });
|
|
1882
1979
|
}
|
|
1883
1980
|
catch (err) {
|
|
1981
|
+
// Nothing went out: the reservation is retracted so the next send is not refused for it.
|
|
1982
|
+
if (turn !== null)
|
|
1983
|
+
chatTurns.abort(id, turn.turnId);
|
|
1884
1984
|
const msg = message(err);
|
|
1885
1985
|
return reply.code(/no warm seats/.test(msg) ? 409 : 400).send({ error: msg });
|
|
1886
1986
|
}
|
|
@@ -1931,6 +2031,9 @@ runtime = {}) {
|
|
|
1931
2031
|
seats: await adapter.chatSeats(id),
|
|
1932
2032
|
scope: chatScopes.get(id) ?? null,
|
|
1933
2033
|
refused: chatScopes.refusedOf(id) ?? null,
|
|
2034
|
+
// DES-L5 (D-13): the transcript so far, append order — `[]` before the first persisted
|
|
2035
|
+
// turn; the file goes with the chat on `chatClosed`, so a reclaimed id answers `[]` too.
|
|
2036
|
+
...(chatTranscripts !== undefined ? { messages: chatTranscripts.read(id) } : {}),
|
|
1934
2037
|
};
|
|
1935
2038
|
}
|
|
1936
2039
|
catch (err) {
|
|
@@ -3235,7 +3338,11 @@ runtime = {}) {
|
|
|
3235
3338
|
// The store is SHARED with the skin (crew#323): beside the engine's own keys it round-trips
|
|
3236
3339
|
// the studio's `studio.*` preference blobs verbatim — see `CrewSystemSettings`'s index
|
|
3237
3340
|
// signature in core/types.ts, which states that rather than leaving it to a client comment.
|
|
3238
|
-
|
|
3341
|
+
// crew#494 (F-007 — FIX-IT-ALL L10-6): the System page showed a LITERAL settings path; the daemon
|
|
3342
|
+
// now says where its settings file actually is (`WICKED_CREW_SYSTEM_SETTINGS` honoured) as the
|
|
3343
|
+
// additive `path` (api-types 0.38.0 `SettingsResponse.path?`; adjudicated §4.6 — `path`, not
|
|
3344
|
+
// `settings_path`).
|
|
3345
|
+
app.get(`${V}/settings`, async () => ({ settings: await adapter.getSettings(), path: settingsFilePath() }));
|
|
3239
3346
|
app.put(`${V}/settings`, async (req, reply) => {
|
|
3240
3347
|
const patch = req.body;
|
|
3241
3348
|
if (typeof patch !== 'object' || patch === null || Array.isArray(patch)) {
|
|
@@ -3309,6 +3416,26 @@ runtime = {}) {
|
|
|
3309
3416
|
.send({ error: "deliverDefault must be 'pr' or 'none'" });
|
|
3310
3417
|
}
|
|
3311
3418
|
}
|
|
3419
|
+
// baseSkillRef / baseSkillPolicy (crew#554 / wicked-core#468): the discipline skill EVERY
|
|
3420
|
+
// governed unit is told to follow, and what a snapshot without it means. Both decide whether
|
|
3421
|
+
// launches carry the directive or are refused at intake, so a typo is a 400, never a silently
|
|
3422
|
+
// dropped key. The name is exported to the engine verbatim: a skill-name shape only.
|
|
3423
|
+
if (Object.hasOwn(patch, 'baseSkillRef')) {
|
|
3424
|
+
const r = patch.baseSkillRef;
|
|
3425
|
+
if (typeof r !== 'string' || !BASE_SKILL_REF_SHAPE.test(r.trim())) {
|
|
3426
|
+
return reply.code(400).send({
|
|
3427
|
+
error: 'baseSkillRef must be a skill name (lowercase letters, digits, "-", "_", ":", ".", up to 128 chars — e.g. "wicked-garden-governed-worker"), or "" to turn the base skill off',
|
|
3428
|
+
});
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
if (Object.hasOwn(patch, 'baseSkillPolicy')) {
|
|
3432
|
+
const p = patch.baseSkillPolicy;
|
|
3433
|
+
if (typeof p !== 'string' || !BASE_SKILL_POLICIES.has(p)) {
|
|
3434
|
+
return reply.code(400).send({
|
|
3435
|
+
error: "baseSkillPolicy must be 'require' — the only policy (the engine refuses launches at intake until the published snapshot holds the base skill); 'warn' was deleted in crew 0.7.35: it ran seats UNGROUNDED. Set baseSkillRef \"\" to turn the base skill off explicitly",
|
|
3436
|
+
});
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3312
3439
|
// Skin-owned keys (crew#323): allowed through, but VALIDATED rather than trusted. The
|
|
3313
3440
|
// daemon does not read these values, so the only two things it can check are the two that
|
|
3314
3441
|
// can hurt it — a value it cannot persist, and a value big enough to bloat settings.json.
|
|
@@ -3349,6 +3476,8 @@ runtime = {}) {
|
|
|
3349
3476
|
'workerStallEscalateAction',
|
|
3350
3477
|
'workerStallMaxEscalations',
|
|
3351
3478
|
'deliverDefault',
|
|
3479
|
+
'baseSkillRef',
|
|
3480
|
+
'baseSkillPolicy',
|
|
3352
3481
|
];
|
|
3353
3482
|
const safe = {};
|
|
3354
3483
|
for (const key of allowed) {
|
|
@@ -3374,6 +3503,12 @@ runtime = {}) {
|
|
|
3374
3503
|
// WICKED_WORKER_HOME per worker spawn — never cached — so this alone makes the change live
|
|
3375
3504
|
// at the next spawn: no daemon restart, no engine restart.
|
|
3376
3505
|
applyWorkerConfigRoot(settings.worker_config_root);
|
|
3506
|
+
// Re-judge and re-export the BASE skill (crew#554) — the engine reads WICKED_BASE_SKILL_REF at
|
|
3507
|
+
// intake, per launch, so this alone makes a changed name or policy live at the next launch.
|
|
3508
|
+
// Only when the patch named either key: an unrelated settings write must not re-log the seam.
|
|
3509
|
+
if (Object.hasOwn(safe, 'baseSkillRef') || Object.hasOwn(safe, 'baseSkillPolicy')) {
|
|
3510
|
+
runtime.skills?.configureBaseSkill(settings);
|
|
3511
|
+
}
|
|
3377
3512
|
// (No skills re-apply: the skills root is not a setting — skills/runtime.ts, codex round 5.)
|
|
3378
3513
|
// `changed` names every persisted key, engine and `studio.*` alike; `ignored` (present only
|
|
3379
3514
|
// when there is one) is where a dropped unknown key stops being invisible.
|