wicked-crew 0.7.46 → 0.7.48
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/capture.d.ts +2 -2
- package/dist/api/chat-citations.d.ts +10 -1
- package/dist/api/chat-citations.d.ts.map +1 -1
- package/dist/api/chat-citations.js +28 -0
- package/dist/api/chat-citations.js.map +1 -1
- package/dist/api/chat-scope.d.ts +4 -2
- package/dist/api/chat-scope.d.ts.map +1 -1
- package/dist/api/chat-scope.js +35 -3
- package/dist/api/chat-scope.js.map +1 -1
- package/dist/api/chat-transcripts.d.ts +23 -1
- package/dist/api/chat-transcripts.d.ts.map +1 -1
- package/dist/api/chat-transcripts.js +61 -6
- package/dist/api/chat-transcripts.js.map +1 -1
- package/dist/api/delivery-index.d.ts +21 -0
- package/dist/api/delivery-index.d.ts.map +1 -1
- package/dist/api/delivery-index.js +36 -0
- package/dist/api/delivery-index.js.map +1 -1
- package/dist/api/elicitation-cache.d.ts +12 -0
- package/dist/api/elicitation-cache.d.ts.map +1 -1
- package/dist/api/elicitation-cache.js +17 -1
- package/dist/api/elicitation-cache.js.map +1 -1
- package/dist/api/eval-sample.d.ts +12 -12
- package/dist/api/governance-steering.d.ts.map +1 -1
- package/dist/api/governance-steering.js +1 -0
- package/dist/api/governance-steering.js.map +1 -1
- package/dist/api/mcp.d.ts +4 -4
- package/dist/api/policy-landing.d.ts +55 -0
- package/dist/api/policy-landing.d.ts.map +1 -0
- package/dist/api/policy-landing.js +174 -0
- package/dist/api/policy-landing.js.map +1 -0
- package/dist/api/recording.d.ts +228 -0
- package/dist/api/recording.d.ts.map +1 -0
- package/dist/api/recording.js +1140 -0
- package/dist/api/recording.js.map +1 -0
- package/dist/api/routes.d.ts +42 -39
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +362 -107
- package/dist/api/routes.js.map +1 -1
- package/dist/api/run-timing-index.d.ts +17 -0
- package/dist/api/run-timing-index.d.ts.map +1 -1
- package/dist/api/run-timing-index.js +43 -1
- package/dist/api/run-timing-index.js.map +1 -1
- package/dist/api/server.d.ts +27 -0
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +168 -4
- package/dist/api/server.js.map +1 -1
- package/dist/api/testing.d.ts +4 -4
- package/dist/api/testing.d.ts.map +1 -1
- package/dist/api/testing.js +3 -0
- package/dist/api/testing.js.map +1 -1
- package/dist/api/watch-routes.d.ts +30 -0
- package/dist/api/watch-routes.d.ts.map +1 -0
- package/dist/api/watch-routes.js +104 -0
- package/dist/api/watch-routes.js.map +1 -0
- package/dist/campaigns/routes.d.ts +2 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +20 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/core/adapter.d.ts +52 -1
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +193 -5
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/run-identity.js +1 -1
- package/dist/core/run-identity.js.map +1 -1
- package/dist/core/walkthrough-root.d.ts +21 -0
- package/dist/core/walkthrough-root.d.ts.map +1 -0
- package/dist/core/walkthrough-root.js +66 -0
- package/dist/core/walkthrough-root.js.map +1 -0
- package/dist/decisions/chat-recorder.d.ts +129 -0
- package/dist/decisions/chat-recorder.d.ts.map +1 -0
- package/dist/decisions/chat-recorder.js +367 -0
- package/dist/decisions/chat-recorder.js.map +1 -0
- package/dist/decisions/consider.d.ts +102 -0
- package/dist/decisions/consider.d.ts.map +1 -0
- package/dist/decisions/consider.js +207 -0
- package/dist/decisions/consider.js.map +1 -0
- package/dist/decisions/derive.d.ts +79 -0
- package/dist/decisions/derive.d.ts.map +1 -0
- package/dist/decisions/derive.js +282 -0
- package/dist/decisions/derive.js.map +1 -0
- package/dist/decisions/events.d.ts +25 -0
- package/dist/decisions/events.d.ts.map +1 -0
- package/dist/decisions/events.js +24 -0
- package/dist/decisions/events.js.map +1 -0
- package/dist/decisions/ingest.d.ts +51 -0
- package/dist/decisions/ingest.d.ts.map +1 -0
- package/dist/decisions/ingest.js +100 -0
- package/dist/decisions/ingest.js.map +1 -0
- package/dist/decisions/land.d.ts +100 -0
- package/dist/decisions/land.d.ts.map +1 -0
- package/dist/decisions/land.js +466 -0
- package/dist/decisions/land.js.map +1 -0
- package/dist/decisions/ledger.d.ts +60 -0
- package/dist/decisions/ledger.d.ts.map +1 -0
- package/dist/decisions/ledger.js +246 -0
- package/dist/decisions/ledger.js.map +1 -0
- package/dist/decisions/routes.d.ts +10 -0
- package/dist/decisions/routes.d.ts.map +1 -0
- package/dist/decisions/routes.js +141 -0
- package/dist/decisions/routes.js.map +1 -0
- package/dist/decisions/rules-text.d.ts +21 -0
- package/dist/decisions/rules-text.d.ts.map +1 -0
- package/dist/decisions/rules-text.js +50 -0
- package/dist/decisions/rules-text.js.map +1 -0
- package/dist/decisions/types.d.ts +120 -0
- package/dist/decisions/types.d.ts.map +1 -0
- package/dist/decisions/types.js +19 -0
- package/dist/decisions/types.js.map +1 -0
- package/dist/editors/csp.d.ts +31 -0
- package/dist/editors/csp.d.ts.map +1 -0
- package/dist/editors/csp.js +46 -0
- package/dist/editors/csp.js.map +1 -0
- package/dist/editors/pack-install.d.ts +28 -0
- package/dist/editors/pack-install.d.ts.map +1 -0
- package/dist/editors/pack-install.js +153 -0
- package/dist/editors/pack-install.js.map +1 -0
- package/dist/editors/registry.d.ts +142 -0
- package/dist/editors/registry.d.ts.map +1 -0
- package/dist/editors/registry.js +255 -0
- package/dist/editors/registry.js.map +1 -0
- package/dist/editors/routes.d.ts +46 -0
- package/dist/editors/routes.d.ts.map +1 -0
- package/dist/editors/routes.js +166 -0
- package/dist/editors/routes.js.map +1 -0
- package/dist/interactive/bridge-pool.d.ts +7 -2
- package/dist/interactive/bridge-pool.d.ts.map +1 -1
- package/dist/interactive/bridge-pool.js +6 -1
- package/dist/interactive/bridge-pool.js.map +1 -1
- package/dist/interactive/draft-events.d.ts.map +1 -1
- package/dist/interactive/draft-events.js +1 -0
- package/dist/interactive/draft-events.js.map +1 -1
- package/dist/interactive/theme-events.d.ts +119 -0
- package/dist/interactive/theme-events.d.ts.map +1 -0
- package/dist/interactive/theme-events.js +606 -0
- package/dist/interactive/theme-events.js.map +1 -0
- package/dist/projects/events.d.ts.map +1 -1
- package/dist/projects/events.js +2 -1
- package/dist/projects/events.js.map +1 -1
- package/dist/projects/routes.d.ts +2 -2
- package/dist/projects/state-home-preflight.d.ts.map +1 -1
- package/dist/projects/state-home-preflight.js +3 -0
- package/dist/projects/state-home-preflight.js.map +1 -1
- package/dist/projects/state-home-registry.d.ts.map +1 -1
- package/dist/projects/state-home-registry.js +2 -0
- package/dist/projects/state-home-registry.js.map +1 -1
- package/dist/projects/state-home.d.ts +6 -0
- package/dist/projects/state-home.d.ts.map +1 -1
- package/dist/projects/state-home.js +28 -1
- package/dist/projects/state-home.js.map +1 -1
- package/dist/qe/acceptance.d.ts +45 -4
- package/dist/qe/acceptance.d.ts.map +1 -1
- package/dist/qe/acceptance.js +93 -4
- package/dist/qe/acceptance.js.map +1 -1
- package/dist/qe/ledger.d.ts +20 -0
- package/dist/qe/ledger.d.ts.map +1 -1
- package/dist/qe/ledger.js +34 -0
- package/dist/qe/ledger.js.map +1 -1
- package/dist/qe/walkthrough-acceptance.d.ts +82 -0
- package/dist/qe/walkthrough-acceptance.d.ts.map +1 -0
- package/dist/qe/walkthrough-acceptance.js +248 -0
- package/dist/qe/walkthrough-acceptance.js.map +1 -0
- package/dist/skills/base-skill.d.ts.map +1 -1
- package/dist/skills/base-skill.js +3 -2
- package/dist/skills/base-skill.js.map +1 -1
- package/dist/skills/plugin-source.d.ts +19 -1
- package/dist/skills/plugin-source.d.ts.map +1 -1
- package/dist/skills/plugin-source.js +31 -2
- package/dist/skills/plugin-source.js.map +1 -1
- package/dist/skills/runtime.d.ts +11 -2
- package/dist/skills/runtime.d.ts.map +1 -1
- package/dist/skills/runtime.js +29 -1
- package/dist/skills/runtime.js.map +1 -1
- package/dist/skills/store.d.ts +14 -1
- package/dist/skills/store.d.ts.map +1 -1
- package/dist/skills/store.js +19 -5
- package/dist/skills/store.js.map +1 -1
- package/dist/standing-orders/routes.d.ts +22 -22
- package/dist/standing-orders/store.d.ts +10 -10
- package/dist/watch/checks/check-failed.d.ts +24 -0
- package/dist/watch/checks/check-failed.d.ts.map +1 -0
- package/dist/watch/checks/check-failed.js +56 -0
- package/dist/watch/checks/check-failed.js.map +1 -0
- package/dist/watch/checks/claim-vs-evidence.d.ts +30 -0
- package/dist/watch/checks/claim-vs-evidence.d.ts.map +1 -0
- package/dist/watch/checks/claim-vs-evidence.js +152 -0
- package/dist/watch/checks/claim-vs-evidence.js.map +1 -0
- package/dist/watch/checks/deliver-audit.d.ts +19 -0
- package/dist/watch/checks/deliver-audit.d.ts.map +1 -0
- package/dist/watch/checks/deliver-audit.js +129 -0
- package/dist/watch/checks/deliver-audit.js.map +1 -0
- package/dist/watch/checks/index.d.ts +8 -0
- package/dist/watch/checks/index.d.ts.map +1 -0
- package/dist/watch/checks/index.js +24 -0
- package/dist/watch/checks/index.js.map +1 -0
- package/dist/watch/checks/lag.d.ts +24 -0
- package/dist/watch/checks/lag.d.ts.map +1 -0
- package/dist/watch/checks/lag.js +55 -0
- package/dist/watch/checks/lag.js.map +1 -0
- package/dist/watch/checks/quiet-after-claim.d.ts +17 -0
- package/dist/watch/checks/quiet-after-claim.d.ts.map +1 -0
- package/dist/watch/checks/quiet-after-claim.js +89 -0
- package/dist/watch/checks/quiet-after-claim.js.map +1 -0
- package/dist/watch/checks/ungated.d.ts +12 -0
- package/dist/watch/checks/ungated.d.ts.map +1 -0
- package/dist/watch/checks/ungated.js +64 -0
- package/dist/watch/checks/ungated.js.map +1 -0
- package/dist/watch/checks/warned-rule.d.ts +26 -0
- package/dist/watch/checks/warned-rule.d.ts.map +1 -0
- package/dist/watch/checks/warned-rule.js +126 -0
- package/dist/watch/checks/warned-rule.js.map +1 -0
- package/dist/watch/emit.d.ts +122 -0
- package/dist/watch/emit.d.ts.map +1 -0
- package/dist/watch/emit.js +443 -0
- package/dist/watch/emit.js.map +1 -0
- package/dist/watch/events.d.ts +21 -0
- package/dist/watch/events.d.ts.map +1 -0
- package/dist/watch/events.js +21 -0
- package/dist/watch/events.js.map +1 -0
- package/dist/watch/keys.d.ts +15 -0
- package/dist/watch/keys.d.ts.map +1 -0
- package/dist/watch/keys.js +23 -0
- package/dist/watch/keys.js.map +1 -0
- package/dist/watch/lanes.d.ts +41 -0
- package/dist/watch/lanes.d.ts.map +1 -0
- package/dist/watch/lanes.js +73 -0
- package/dist/watch/lanes.js.map +1 -0
- package/dist/watch/loader.d.ts +47 -0
- package/dist/watch/loader.d.ts.map +1 -0
- package/dist/watch/loader.js +178 -0
- package/dist/watch/loader.js.map +1 -0
- package/dist/watch/registry.d.ts +166 -0
- package/dist/watch/registry.d.ts.map +1 -0
- package/dist/watch/registry.js +589 -0
- package/dist/watch/registry.js.map +1 -0
- package/dist/watch/ring.d.ts +32 -0
- package/dist/watch/ring.d.ts.map +1 -0
- package/dist/watch/ring.js +90 -0
- package/dist/watch/ring.js.map +1 -0
- package/dist/watch/router.d.ts +25 -0
- package/dist/watch/router.d.ts.map +1 -0
- package/dist/watch/router.js +87 -0
- package/dist/watch/router.js.map +1 -0
- package/dist/watch/sources.d.ts +42 -0
- package/dist/watch/sources.d.ts.map +1 -0
- package/dist/watch/sources.js +104 -0
- package/dist/watch/sources.js.map +1 -0
- package/dist/watch/types.d.ts +129 -0
- package/dist/watch/types.d.ts.map +1 -0
- package/dist/watch/types.js +6 -0
- package/dist/watch/types.js.map +1 -0
- package/endpoint-manifest.json +272 -1
- package/package.json +3 -2
- package/watch/entries/claim-vs-evidence.json +13 -0
- package/watch/entries/deliver-audit.json +17 -0
- package/watch/entries/quiet-after-claim.json +16 -0
- package/watch/entries/registry-check-failed.json +12 -0
- package/watch/entries/registry-lagging.json +12 -0
- package/watch/entries/risky-call.json +12 -0
- package/watch/entries/ungated.json +13 -0
- package/dist/api/demo.d.ts +0 -90
- package/dist/api/demo.d.ts.map +0 -1
- package/dist/api/demo.js +0 -492
- package/dist/api/demo.js.map +0 -1
package/dist/api/routes.js
CHANGED
|
@@ -38,7 +38,7 @@ import { DEFAULT_STEERING_TYPE, STEERING_TYPE_VALUES, STEERING_TYPES, registerGo
|
|
|
38
38
|
import { isSteeringAuthorRun, landSteeringProposal } from './steering-landing.js';
|
|
39
39
|
import { ApproveEditSchema, approveEdited, captureLaunchRoots, editInFlight, FileProposalSchema, registerCaptureRoutes } from './capture.js';
|
|
40
40
|
import { registerTestingRoutes } from './testing.js';
|
|
41
|
-
import { DEMO_PRESET, demoLaunchRoots, registerDemoRoutes } from './
|
|
41
|
+
import { DEMO_PRESET, demoLaunchRoots, registerDemoRoutes, registerWalkthroughRoutes, walkthroughAcceptance } from './recording.js';
|
|
42
42
|
import { registerSkillsRoutes } from './skills.js';
|
|
43
43
|
import { registerMcpRoutes } from './mcp.js';
|
|
44
44
|
import { McpPolicies } from '../mcp/policies.js';
|
|
@@ -46,8 +46,10 @@ import { disabledSkillsHealth } from '../skills/runtime.js';
|
|
|
46
46
|
import { phaseSkillFindings, withPhaseSkillGaps } from '../skills/phase-skill-gaps.js';
|
|
47
47
|
import { STATE_HOME_BLOCKER_CODE, STATE_HOME_REMEDY, isEngineStateHomeRefusal, stateHomeBlockerBody, } from '../projects/state-home-preflight.js';
|
|
48
48
|
import { BASE_SKILL_POLICIES, BASE_SKILL_REF_SHAPE, baseSkillRemedy } from '../skills/base-skill.js';
|
|
49
|
+
import { REQUIRED_GARDEN_VERSION } from '../skills/plugin-source.js';
|
|
49
50
|
import { noEligibleSeatBody, parseNoEligibleSeat } from '../core/engine-roster.js';
|
|
50
51
|
import { ProjectSettingsStore } from '../projects/settings.js';
|
|
52
|
+
import { isHumanOperator } from './watch-routes.js';
|
|
51
53
|
import { boundOrigin, InteractiveBridgePool } from '../interactive/bridge-pool.js';
|
|
52
54
|
import { composeDeliverText, configuredPublicOrigin, extractFollowUps, factsFromRun, FOLLOW_UPS_MAX, framedDeliverText, runUrlFor, } from '../core/deliver-text.js';
|
|
53
55
|
import { deliverRepoFor, deliverTargetView, isGitHubLogin, readDeliverOriginUrl, resolvePullRequest as resolvePullRequestViaGh, } from '../core/deliver.js';
|
|
@@ -69,14 +71,25 @@ import { DeliveryFreeze, frozenRefusal, isDeliverUnit, registerDeliveryFreezeRou
|
|
|
69
71
|
import { GroupIndex } from './group-index.js';
|
|
70
72
|
import { RunTimingIndex, recordRunLaunched } from './run-timing-index.js';
|
|
71
73
|
import { GuidanceIndex } from './guidance-index.js';
|
|
72
|
-
import { DeliveryIndex, gitRunBranchIsEmpty, gitWorktreeIsClean, isDeliverConflictStranded, deliveryRecordFrom, pushedState, canDeliverResolver, } from './delivery-index.js';
|
|
74
|
+
import { DELIVER_REQUESTED_ACTION, DeliveryIndex, gitRunBranchIsEmpty, gitWorktreeIsClean, isDeliverConflictStranded, deliveryRecordFrom, pushedState, canDeliverResolver, FREE_TEXT_NOTICE, freeTextOutcome, } from './delivery-index.js';
|
|
73
75
|
import { DeliveryDerivationCache } from './delivery-cache.js';
|
|
74
76
|
import { gitReprovisionWorktree, runDeliverScript, } from './post-hoc-deliver.js';
|
|
75
|
-
import { LOCAL_ACTOR } from './auth.js';
|
|
77
|
+
import { LOCAL_ACTOR, trustAtLeast } from './auth.js';
|
|
76
78
|
// Re-exported so existing `import { API_PREFIX } from './routes.js'` callers keep working; the
|
|
77
79
|
// value lives in the leaf module api-prefix.ts to keep unit-output.ts out of this file's cycle.
|
|
78
80
|
export { API_PREFIX } from './api-prefix.js';
|
|
79
81
|
import { API_PREFIX } from './api-prefix.js';
|
|
82
|
+
import { landPolicyRule, policyProposalToRule } from './policy-landing.js';
|
|
83
|
+
import { DecisionError, DecisionService } from '../decisions/land.js';
|
|
84
|
+
import { ingestDecision } from '../decisions/ingest.js';
|
|
85
|
+
import { registerDecisionRoutes } from '../decisions/routes.js';
|
|
86
|
+
import { ChatDecisionRecorder } from '../decisions/chat-recorder.js';
|
|
87
|
+
import { ConsiderationService } from '../decisions/consider.js';
|
|
88
|
+
import { gardenPackCheck } from '../editors/pack-install.js';
|
|
89
|
+
import { EditorRegistry } from '../editors/registry.js';
|
|
90
|
+
import { registerEditorRoutes } from '../editors/routes.js';
|
|
91
|
+
import { discoverLivePlugin } from '../skills/plugin-source.js';
|
|
92
|
+
import { resolveDecisionsMode } from '../decisions/types.js';
|
|
80
93
|
const V = API_PREFIX;
|
|
81
94
|
/** How long `GET /health` waits on the engine's actor before answering `ping: "busy"` (crew#471). */
|
|
82
95
|
export const HEALTH_PING_WAIT_MS = 1_000;
|
|
@@ -185,80 +198,8 @@ function isStringRecord(v) {
|
|
|
185
198
|
!Array.isArray(v) &&
|
|
186
199
|
Object.values(v).every((x) => typeof x === 'string'));
|
|
187
200
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
* ConformanceRule (DES-MEM-FACETED-001 §5.2). A policy proposal's `kind_type` is
|
|
191
|
-
* `policy:<steering_type>` and its `payload` is `{ rule, severity }`; estate writes NOTHING for it
|
|
192
|
-
* (the AW-11 "no rules.write on estate" invariant) and hands the payload back for crew — the ONE
|
|
193
|
-
* governed rules-write path — to land. Returns the rule to upsert plus the resolved steering type,
|
|
194
|
-
* or a loud `error` string when the proposal cannot be shaped into a valid rule (a malformed
|
|
195
|
-
* `kind_type` / missing `rule` / out-of-enum `severity`) — the caller reports it as a failed
|
|
196
|
-
* landing, never a silent drop (the crew#388 anti-silent-loss doctrine).
|
|
197
|
-
*
|
|
198
|
-
* The rule id is DETERMINISTIC (`proposal:<id>`) so a re-driven landing UPSERTS the same rule
|
|
199
|
-
* (idempotent) instead of minting a duplicate; it sits OUTSIDE the reserved `PAT-/POL-` namespace,
|
|
200
|
-
* which UI/chat/proposal-authored rules are free to do (INV-C1). The rule carries NO `effect`
|
|
201
|
-
* (recall-only — exactly what `{rule, severity}` supports: an enforcement rule would need a
|
|
202
|
-
* non-blank `applies_to`, which the payload does not carry, and INV-S3 fails such a rule closed).
|
|
203
|
-
* `steering_type` is validated against the vocabulary here so the engine's INV-S1 never rejects it
|
|
204
|
-
* as an unknown page.
|
|
205
|
-
*/
|
|
206
|
-
export function policyProposalToRule(proposalId, kindType, payload, facets) {
|
|
207
|
-
const steeringType = kindType.startsWith('policy:') ? kindType.slice('policy:'.length) : '';
|
|
208
|
-
if (!STEERING_TYPES.has(steeringType)) {
|
|
209
|
-
return {
|
|
210
|
-
error: `the approved proposal's kind_type ${JSON.stringify(kindType)} does not name a steering ` +
|
|
211
|
-
`type — expected \`policy:<${STEERING_TYPE_VALUES.join('|')}>\``,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
const body = typeof payload === 'object' && payload !== null && !Array.isArray(payload)
|
|
215
|
-
? payload
|
|
216
|
-
: {};
|
|
217
|
-
const statement = typeof body['rule'] === 'string' ? body['rule'].trim() : '';
|
|
218
|
-
if (statement === '') {
|
|
219
|
-
return {
|
|
220
|
-
error: 'the approved policy proposal payload has no `rule` string to make a rule statement from',
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
const rawSeverity = body['severity'];
|
|
224
|
-
// Normalize common LLM drift before the enum check: a model naturally writes the English word
|
|
225
|
-
// "warning", but the engine's enum is the short "warn" (info/error/critical are already the
|
|
226
|
-
// natural words). Tolerate case/whitespace too. A capture worker that says "warning" must still
|
|
227
|
-
// land its policy — otherwise every derived policy at the middle band silently fails to land.
|
|
228
|
-
const normSeverity = typeof rawSeverity === 'string'
|
|
229
|
-
? (() => {
|
|
230
|
-
const s = rawSeverity.trim().toLowerCase();
|
|
231
|
-
return s === 'warning' ? 'warn' : s;
|
|
232
|
-
})()
|
|
233
|
-
: rawSeverity;
|
|
234
|
-
const severity = normSeverity === 'info' || normSeverity === 'warn' || normSeverity === 'error' || normSeverity === 'critical'
|
|
235
|
-
? normSeverity
|
|
236
|
-
: undefined;
|
|
237
|
-
if (rawSeverity !== undefined && severity === undefined) {
|
|
238
|
-
return {
|
|
239
|
-
error: `the approved policy proposal payload has an invalid severity ${JSON.stringify(rawSeverity)} — ` +
|
|
240
|
-
'expected info|warn|error|critical (or the natural "warning" for warn)',
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
const language = facets?.['language'];
|
|
244
|
-
const rule = {
|
|
245
|
-
id: `proposal:${proposalId}`,
|
|
246
|
-
rule_type: 'policy',
|
|
247
|
-
statement,
|
|
248
|
-
// A policy proposal SHOULD carry severity; a missing one defaults to `warn` (the middle band)
|
|
249
|
-
// rather than failing the landing, but a present-but-garbage one fails loud above.
|
|
250
|
-
severity: severity ?? 'warn',
|
|
251
|
-
// REQUIRED engine-side (f32, no serde default, INV-C2 `[0,1]`); the payload carries none, so a
|
|
252
|
-
// fixed authority — the same 0.8 the steering-author landing defaults to.
|
|
253
|
-
confidence: 0.8,
|
|
254
|
-
// Only the `language` facet maps onto the engine's Targets facet object; `repo`/`project`
|
|
255
|
-
// facets have no ConformanceRule slot and are dropped (see the route's open question).
|
|
256
|
-
targets: typeof language === 'string' && language !== '' ? { language } : {},
|
|
257
|
-
provenance: { source: 'proposal', source_kinds: [] },
|
|
258
|
-
steering_type: steeringType,
|
|
259
|
-
};
|
|
260
|
-
return { rule, steeringType: steeringType };
|
|
261
|
-
}
|
|
201
|
+
// `policyProposalToRule` lives beside THE landing (policy-landing.ts); re-exported for its callers.
|
|
202
|
+
export { policyProposalToRule } from './policy-landing.js';
|
|
262
203
|
/** A JSON object whose every value is a number (a coverage breakdown map). */
|
|
263
204
|
function isNumberRecord(v) {
|
|
264
205
|
return (typeof v === 'object' &&
|
|
@@ -741,6 +682,62 @@ runtime = {}) {
|
|
|
741
682
|
// default is the real spawn-per-call `wicked-estate-mcp` client; route tests inject a stub so no
|
|
742
683
|
// process is ever spawned.
|
|
743
684
|
const estateTool = runtime.callEstateTool ?? callEstateTool;
|
|
685
|
+
// DC-S4a: decision capture over the structured hosts (gate note, elicitation, inject). Words are
|
|
686
|
+
// recorded post-commit, off the request path, and only from a human actor (decisions/ingest.ts).
|
|
687
|
+
const decisions = runtime.decisionLedger !== undefined
|
|
688
|
+
? new DecisionService({
|
|
689
|
+
ledger: runtime.decisionLedger,
|
|
690
|
+
mode: runtime.decisionsMode ?? resolveDecisionsMode(),
|
|
691
|
+
authMode: security.authMode,
|
|
692
|
+
estateTool,
|
|
693
|
+
adapter,
|
|
694
|
+
audit,
|
|
695
|
+
emit: runtime.decisionsEmit ?? null,
|
|
696
|
+
...(runtime.broadcast !== undefined
|
|
697
|
+
? { broadcast: (frame) => runtime.broadcast?.(frame) }
|
|
698
|
+
: {}),
|
|
699
|
+
log: (m) => app.log.warn(m),
|
|
700
|
+
})
|
|
701
|
+
: null;
|
|
702
|
+
if (decisions !== null)
|
|
703
|
+
registerDecisionRoutes(app, decisions);
|
|
704
|
+
// DC-S4b: the studio chat host. The recorder writes the transcript's `decisions` record and the
|
|
705
|
+
// `chatDecisions` frame, so without the store or the /ws fan-out there is no chat host at all.
|
|
706
|
+
// DC-S7: considered · set aside · cited (unchecked) for chat turns and run units, and how the
|
|
707
|
+
// seats learn the rules (the statement at open, a disclosed preface on the next send after a rule
|
|
708
|
+
// lands). Fail-soft on an engine or a stub without the rule reads.
|
|
709
|
+
const considerations = new ConsiderationService({
|
|
710
|
+
adapter,
|
|
711
|
+
...(runtime.decisionLedger !== undefined ? { ledger: runtime.decisionLedger } : {}),
|
|
712
|
+
...(chatTranscripts !== undefined ? { transcripts: chatTranscripts } : {}),
|
|
713
|
+
projectOf: (id) => projects.index.projectOf(id),
|
|
714
|
+
emit: runtime.decisionsEmit ?? null,
|
|
715
|
+
log: (m) => app.log.warn(m),
|
|
716
|
+
});
|
|
717
|
+
// EP-C1 (DES-artifact-editor-plugins §9.1): the editor registry — first-party editors discovered
|
|
718
|
+
// in studio's bundle, flags in `daemon-editors.json` — and its routes. Pack installs are refused
|
|
719
|
+
// until EP-C8, after garden's gate has run and the approval list has been read.
|
|
720
|
+
const editorRegistry = new EditorRegistry({
|
|
721
|
+
...(runtime.editorsStateHome !== undefined ? { stateHome: runtime.editorsStateHome } : {}),
|
|
722
|
+
...(runtime.studioRoot !== undefined ? { studioRoot: runtime.studioRoot } : {}),
|
|
723
|
+
log: (m) => app.log.warn(m),
|
|
724
|
+
});
|
|
725
|
+
registerEditorRoutes(app, {
|
|
726
|
+
registry: editorRegistry,
|
|
727
|
+
adapter,
|
|
728
|
+
packCheck: runtime.packCheck ?? gardenPackCheck(discoverLivePlugin()?.path ?? null),
|
|
729
|
+
log: (m) => app.log.warn(m),
|
|
730
|
+
});
|
|
731
|
+
const chatRecorder = decisions !== null && chatTranscripts !== undefined && runtime.broadcast !== undefined
|
|
732
|
+
? new ChatDecisionRecorder({
|
|
733
|
+
service: decisions,
|
|
734
|
+
transcripts: chatTranscripts,
|
|
735
|
+
broadcast: runtime.broadcast,
|
|
736
|
+
projectOf: (chat) => projects.index.projectOf(chat),
|
|
737
|
+
onTurnRecorded: (chat, turnId) => considerations.onChatTurn(chat, turnId),
|
|
738
|
+
log: (m) => app.log.warn(m),
|
|
739
|
+
})
|
|
740
|
+
: null;
|
|
744
741
|
/** Repo root for a repo ref, from the registry — shared by the reprovision path below. */
|
|
745
742
|
const repoRootOf = async (repoRef) => (await adapter.listRepos()).find((r) => r.id === repoRef)?.root_path;
|
|
746
743
|
/** DES-L9: PR number → head branch, through `gh pr view` unless the runtime injected an answerer. */
|
|
@@ -875,6 +872,20 @@ runtime = {}) {
|
|
|
875
872
|
view.session.launch_actor = launchActor;
|
|
876
873
|
const state = resolveDelivery(view, conflictStrand);
|
|
877
874
|
view.session.delivery = state.delivery;
|
|
875
|
+
// crew#762: was delivery asked for? The launch's decision (`deliver: 'pr'`), a post-hoc attempt
|
|
876
|
+
// or a delivery on record says yes; a launch that said `deliver: 'none'` with neither says no.
|
|
877
|
+
// ABSENT when the daemon holds no launch record and no attempt (never fabricated).
|
|
878
|
+
const launchDeliver = runTimingIndex.launchDeliverFor(view.session.id);
|
|
879
|
+
if (launchDeliver === 'pr' || deliveryIndex.wasRequested(view.session.id) || deliveryIndex.isDelivered(view.session.id)) {
|
|
880
|
+
view.session.deliver_requested = true;
|
|
881
|
+
}
|
|
882
|
+
else if (launchDeliver === 'none') {
|
|
883
|
+
view.session.deliver_requested = false;
|
|
884
|
+
}
|
|
885
|
+
// crew#755: a completed repo-less free-text run answered in text and changed nothing — say so.
|
|
886
|
+
const outcome = freeTextOutcome(view);
|
|
887
|
+
if (outcome !== undefined)
|
|
888
|
+
view.session.outcome = outcome;
|
|
878
889
|
if (state.deliverUrl !== undefined)
|
|
879
890
|
view.session.deliverUrl = state.deliverUrl;
|
|
880
891
|
if (state.deliverBranch !== undefined)
|
|
@@ -888,6 +899,10 @@ runtime = {}) {
|
|
|
888
899
|
const chatGrounded = runTimingIndex.chatGroundedFor(view.session.id);
|
|
889
900
|
if (chatGrounded !== undefined)
|
|
890
901
|
view.session.chat_grounded = chatGrounded;
|
|
902
|
+
// C1: the chat the run was launched from, on every run (terminal ones too) — ABSENT otherwise.
|
|
903
|
+
const chatId = runTimingIndex.chatIdFor(view.session.id);
|
|
904
|
+
if (chatId !== undefined)
|
|
905
|
+
view.session.chat_id = chatId;
|
|
891
906
|
return view;
|
|
892
907
|
};
|
|
893
908
|
// Resolved ONCE and shared by the project routes (which read/write `interactiveRoot`) and the
|
|
@@ -898,6 +913,17 @@ runtime = {}) {
|
|
|
898
913
|
// driving this function directly (tests) has no hooks, so downstream code
|
|
899
914
|
// still gets the ONE actor shape via this accessor.
|
|
900
915
|
const actorOf = (req) => req.actor ?? LOCAL_ACTOR;
|
|
916
|
+
// crew#753: the blocking `skills.garden` finding (an installed garden older than the minimum), or
|
|
917
|
+
// `null`. Read from the skills runtime's cached health; no seam ⇒ nothing to judge.
|
|
918
|
+
const gardenBlocker = () => {
|
|
919
|
+
const f = runtime.skills?.health().findings.find((x) => x.kind === 'skills.garden' && x.severity === 'error');
|
|
920
|
+
return f === undefined ? null : { kind: f.kind, severity: f.severity, message: f.message };
|
|
921
|
+
};
|
|
922
|
+
/** The typed 422 a launch or onboard answers while {@link gardenBlocker} holds. */
|
|
923
|
+
const gardenRefusalBody = () => {
|
|
924
|
+
const f = gardenBlocker();
|
|
925
|
+
return f === null ? null : { code: 'garden_required', error: f.message, required: REQUIRED_GARDEN_VERSION };
|
|
926
|
+
};
|
|
901
927
|
// Liveness — also proves the actor + event pump are up.
|
|
902
928
|
//
|
|
903
929
|
// crew#471: the engine's ping is a round trip through its single-writer actor, and a launch that
|
|
@@ -927,9 +953,15 @@ runtime = {}) {
|
|
|
927
953
|
// F-E2E-030: what this deployment can keep. A composer reads `capabilities.deliverGate`
|
|
928
954
|
// before it promises "pauses at the deliver gate"; a stub-driven route set without the
|
|
929
955
|
// probe honestly reports no gate.
|
|
930
|
-
const capabilities =
|
|
931
|
-
|
|
932
|
-
|
|
956
|
+
const capabilities = {
|
|
957
|
+
...(typeof adapter.engineCapabilities === 'function'
|
|
958
|
+
? adapter.engineCapabilities()
|
|
959
|
+
: { deliverGate: false, revisesPr: false, chatIdOnLaunch: false, seatChipOnCreate: false }),
|
|
960
|
+
// C1: `AgentSession.chat_id` is served from the daemon's own launch index, whatever the engine.
|
|
961
|
+
runChatId: true,
|
|
962
|
+
// WT-W1: repo-bound launches mint an evidence root only on an addon that takes the field.
|
|
963
|
+
walkthroughRoots: typeof adapter.evidenceRootsSupported === 'function' ? adapter.evidenceRootsSupported() : false,
|
|
964
|
+
};
|
|
933
965
|
// wicked-core#411 / crew#497: the state-home blocker rides the health probe as a WARNING. The
|
|
934
966
|
// daemon still SERVES (status stays ok — studio must load and show the blocker) but refuses to
|
|
935
967
|
// launch while the state home holds an entry the worker Read fence cannot classify. Re-surveyed
|
|
@@ -950,6 +982,8 @@ runtime = {}) {
|
|
|
950
982
|
const warnings = [
|
|
951
983
|
...(stateHome === null ? [] : stateHome.findings.map((f) => ({ kind: f.kind, severity: f.severity, message: f.message }))),
|
|
952
984
|
...(baseSkill?.finding ? [{ kind: baseSkill.finding.kind, severity: baseSkill.finding.severity, message: baseSkill.finding.message }] : []),
|
|
985
|
+
// crew#753: a garden older than the minimum is a blocking finding on the first screen too.
|
|
986
|
+
...[gardenBlocker()].filter((f) => f !== null),
|
|
953
987
|
...phaseSkillFindings(runtime.phaseSkills?.gaps() ?? []),
|
|
954
988
|
// DES-TEAMING-002 T0: the daemon's bus did not open at boot, so the engine was handed none
|
|
955
989
|
// and runs un-teamed. A daemon-level notice (status stays ok: it serves); the per-run
|
|
@@ -1015,6 +1049,8 @@ runtime = {}) {
|
|
|
1015
1049
|
// Is the governance evidence LANDING (crew#495): the store, the records on it, the dead
|
|
1016
1050
|
// letters — with a `governance.deadletter` finding the moment the outbox holds one.
|
|
1017
1051
|
governance,
|
|
1052
|
+
// DC-S4b: the chat recorder's per-CLI `wicked-decisions` compliance (null without the chat host).
|
|
1053
|
+
decisions: chatRecorder?.diagnostics() ?? null,
|
|
1018
1054
|
// The state-home classification (wicked-core#411 / crew#497): which state home the worker
|
|
1019
1055
|
// Read fence classifies, every entry it cannot classify there (top level and skills root),
|
|
1020
1056
|
// who classified (the engine, or crew's registry copy on an older addon) and whether that
|
|
@@ -1369,11 +1405,39 @@ runtime = {}) {
|
|
|
1369
1405
|
return message(err);
|
|
1370
1406
|
}
|
|
1371
1407
|
};
|
|
1408
|
+
/**
|
|
1409
|
+
* crew#753: what refuses an onboarding launch, judged BEFORE anything is registered — so a
|
|
1410
|
+
* refused onboard leaves no repo row (core has no unregister). The same three intake refusals a
|
|
1411
|
+
* launch meets: a garden older than the minimum (422 `garden_required`), the state-home blocker
|
|
1412
|
+
* (409) and a base skill the handed generation lacks, e.g. no garden at all (422
|
|
1413
|
+
* `base_skill_refused`). `null` when the launch would be admitted.
|
|
1414
|
+
*/
|
|
1415
|
+
const onboardRefusal = async () => {
|
|
1416
|
+
const garden = gardenRefusalBody();
|
|
1417
|
+
if (garden !== null)
|
|
1418
|
+
return { status: 422, body: garden };
|
|
1419
|
+
if (runtime.stateHome !== undefined) {
|
|
1420
|
+
const stateHome = await runtime.stateHome.refresh();
|
|
1421
|
+
if (stateHome.refusesLaunches)
|
|
1422
|
+
return { status: 409, body: stateHomeBlockerBody(stateHome) };
|
|
1423
|
+
}
|
|
1424
|
+
const posture = runtime.skills?.baseSkill() ?? null;
|
|
1425
|
+
if (posture !== null && !posture.present && posture.finding !== null) {
|
|
1426
|
+
return {
|
|
1427
|
+
status: 422,
|
|
1428
|
+
body: { code: 'base_skill_refused', error: posture.finding.message, baseSkill: posture, remedy: baseSkillRemedy(posture.inCatalog) },
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
return null;
|
|
1432
|
+
};
|
|
1372
1433
|
app.post(`${V}/repos`, async (req, reply) => {
|
|
1373
1434
|
const parsed = RegisterRepoSchema.safeParse(req.body);
|
|
1374
1435
|
if (!parsed.success) {
|
|
1375
1436
|
return reply.code(400).send(invalidBody(parsed.error, 'Invalid request body'));
|
|
1376
1437
|
}
|
|
1438
|
+
const refused = await onboardRefusal();
|
|
1439
|
+
if (refused !== null)
|
|
1440
|
+
return reply.code(refused.status).send(refused.body);
|
|
1377
1441
|
const { name, rootPath, gitUrl, projectId, channel, actor } = parsed.data;
|
|
1378
1442
|
let repo;
|
|
1379
1443
|
let runId;
|
|
@@ -1416,6 +1480,9 @@ runtime = {}) {
|
|
|
1416
1480
|
const body = OnboardBodySchema.safeParse(req.body ?? {});
|
|
1417
1481
|
if (!body.success)
|
|
1418
1482
|
return reply.code(400).send(invalidBody(body.error, 'Invalid request body'));
|
|
1483
|
+
const refused = await onboardRefusal();
|
|
1484
|
+
if (refused !== null)
|
|
1485
|
+
return reply.code(refused.status).send(refused.body);
|
|
1419
1486
|
let runId;
|
|
1420
1487
|
try {
|
|
1421
1488
|
runId = await adapter.launchOnboardingRun(repo.id, repo.name);
|
|
@@ -1449,7 +1516,7 @@ runtime = {}) {
|
|
|
1449
1516
|
return reply.code(400).send(invalidBody(parsed.error, 'Invalid request body'));
|
|
1450
1517
|
}
|
|
1451
1518
|
const b = parsed.data;
|
|
1452
|
-
// The demo preset launches only from POST /projects/:id/demo (api/
|
|
1519
|
+
// The demo preset launches only from POST /projects/:id/demo (api/recording.ts), which mints the run's
|
|
1453
1520
|
// demo root and brief first: launched here directly it would have neither.
|
|
1454
1521
|
if (b.workflow === DEMO_PRESET && (b.sessionId === undefined || !demoLaunchRoots.has(b.sessionId))) {
|
|
1455
1522
|
return reply.code(400).send({
|
|
@@ -1468,6 +1535,10 @@ runtime = {}) {
|
|
|
1468
1535
|
return reply.code(409).send(stateHomeBlockerBody(stateHome));
|
|
1469
1536
|
}
|
|
1470
1537
|
}
|
|
1538
|
+
// crew#753: a garden older than the minimum refuses the launch by name (found vs required).
|
|
1539
|
+
const garden = gardenRefusalBody();
|
|
1540
|
+
if (garden !== null)
|
|
1541
|
+
return reply.code(422).send(garden);
|
|
1471
1542
|
// crew#645: an expired login reads signed out BEFORE routing — wait (bounded) for every seat's
|
|
1472
1543
|
// missing or stale login check, so the standing below is the seats' own answer.
|
|
1473
1544
|
if (b.clisJson === undefined)
|
|
@@ -1485,7 +1556,7 @@ runtime = {}) {
|
|
|
1485
1556
|
const captureRoot = captureLaunchRoots.get(input.sessionId);
|
|
1486
1557
|
if (captureRoot !== undefined)
|
|
1487
1558
|
input.extraWriteRoots = [captureRoot];
|
|
1488
|
-
// A demo launch (api/
|
|
1559
|
+
// A demo launch (api/recording.ts) writes every file into the demo root the demo route minted for it.
|
|
1489
1560
|
const demoRoot = demoLaunchRoots.get(input.sessionId);
|
|
1490
1561
|
if (demoRoot !== undefined)
|
|
1491
1562
|
input.extraWriteRoots = [demoRoot];
|
|
@@ -1730,7 +1801,11 @@ runtime = {}) {
|
|
|
1730
1801
|
// is always reproducible even if the chat is idle-reclaimed before the run finishes.
|
|
1731
1802
|
if (b.chatId !== undefined)
|
|
1732
1803
|
runtime.linkChatRun?.(b.chatId, runId);
|
|
1733
|
-
|
|
1804
|
+
// crew#755: a launch that named neither a workflow nor a plan runs the engine's free-text
|
|
1805
|
+
// planner (one unit, the brief verbatim). Say so in the answer, with how to get work done,
|
|
1806
|
+
// instead of letting it pass for a launch of real work.
|
|
1807
|
+
const freeText = input.workflow === undefined && input.plan === undefined ? { freeText: { notice: FREE_TEXT_NOTICE } } : {};
|
|
1808
|
+
return reply.code(201).send({ runId, ...(linkedIssues !== undefined ? { linkedIssues } : {}), ...freeText });
|
|
1734
1809
|
}
|
|
1735
1810
|
catch (err) {
|
|
1736
1811
|
const msg = message(err);
|
|
@@ -2024,6 +2099,10 @@ runtime = {}) {
|
|
|
2024
2099
|
});
|
|
2025
2100
|
}
|
|
2026
2101
|
deliverInFlight.add(id);
|
|
2102
|
+
// crew#762: the attempt is the durable fact "delivery was requested" — recorded BEFORE the
|
|
2103
|
+
// script runs, so a lift that then fails still reads requested after a reload or restart.
|
|
2104
|
+
audit.record(DELIVER_REQUESTED_ACTION, actorOf(req), { runId: id, detail: { via: 'post-hoc' } });
|
|
2105
|
+
deliveryIndex.markRequested(id);
|
|
2027
2106
|
// The worktree the script runs in. Usually the run's own; but the engine REAPS a
|
|
2028
2107
|
// failed-deliver run's worktree once its work is committed (crew#418/#432) — the work then lives
|
|
2029
2108
|
// on the `wicked/<id>` branch. For such a strand, stand a throwaway worktree back up from
|
|
@@ -2254,6 +2333,8 @@ runtime = {}) {
|
|
|
2254
2333
|
: `chat ${chatId} is already open on this daemon; DELETE /chats/${chatId} first, or omit chatId to mint a fresh one`,
|
|
2255
2334
|
});
|
|
2256
2335
|
}
|
|
2336
|
+
// DC-S7: the in-force rules the statement lists; recorded as "told" only once the open succeeds.
|
|
2337
|
+
let inForceAtOpen = [];
|
|
2257
2338
|
// Everything below either ends in `chatScopes.set(chatId, …, token)` or releases the reservation.
|
|
2258
2339
|
try {
|
|
2259
2340
|
// The scope (crew#502): explicit repos (`repoRefs`, the legacy `repoRef` merged in) or the
|
|
@@ -2280,7 +2361,10 @@ runtime = {}) {
|
|
|
2280
2361
|
const { scope, engine } = resolution;
|
|
2281
2362
|
try {
|
|
2282
2363
|
// Cleans up only what it created itself on failure (Copilot, #518).
|
|
2283
|
-
|
|
2364
|
+
// DC-S7: the seats learn the project's in-force rules from the statement (cap 20, severity
|
|
2365
|
+
// order); the service remembers what they were told so a later landing can be prefaced.
|
|
2366
|
+
inForceAtOpen = (await considerations.inForceFor(scope.projectId ?? null)).rules;
|
|
2367
|
+
prepareChatScratch(chatId, scope, inForceAtOpen);
|
|
2284
2368
|
}
|
|
2285
2369
|
catch (err) {
|
|
2286
2370
|
chatScopes.release(chatId, token);
|
|
@@ -2459,6 +2543,9 @@ runtime = {}) {
|
|
|
2459
2543
|
const roots = scope.repos.map((r) => ({ absRoot: resolve(r.rootPath), name: r.name }));
|
|
2460
2544
|
chatTranscripts.registerRoots(chatId, roots);
|
|
2461
2545
|
}
|
|
2546
|
+
// DC-S7: only a chat that actually OPENED is preface-eligible — recorded here, after the scope
|
|
2547
|
+
// is published, so a refused open leaves no per-chat state behind (codex r1).
|
|
2548
|
+
considerations.noteChatOpen(chatId, inForceAtOpen);
|
|
2462
2549
|
// The thread learns of every refused seat the way it learns of everything else — a frame
|
|
2463
2550
|
// on /ws — AFTER the scope is published, so a reader never sees a refusal for a chat it
|
|
2464
2551
|
// cannot yet look up.
|
|
@@ -2549,12 +2636,20 @@ runtime = {}) {
|
|
|
2549
2636
|
// Sync, same tick as `inFlight`: the window is closed before anything yields.
|
|
2550
2637
|
const turn = chatTurns.begin(id, audience, parsed.data.text);
|
|
2551
2638
|
try {
|
|
2552
|
-
|
|
2639
|
+
// DC-S7: a rule remembered since the seats last heard from crew rides this send as a
|
|
2640
|
+
// crew-authored preface — disclosed below as a `system` transcript record; the `user` record
|
|
2641
|
+
// keeps the operator's own words.
|
|
2642
|
+
const preface = await considerations.prefaceForSend(id);
|
|
2643
|
+
const seats = await adapter.chatSend(id, preface !== null ? `${preface}\n\n${parsed.data.text}` : parsed.data.text, parsed.data.targets);
|
|
2553
2644
|
if (turn !== null) {
|
|
2554
2645
|
// The engine's answer is the truth: the reserved audience is squared with the seats it
|
|
2555
2646
|
// reached, and the operator's message joins the transcript with exactly those seats.
|
|
2556
2647
|
chatTurns.reconcile(id, turn.turnId, seats);
|
|
2557
2648
|
chatTranscripts?.appendUser(id, turn.turnId, parsed.data.text, seats);
|
|
2649
|
+
// DC-S4b: who said it, to whom — the recorder matches the seats' quotes against THIS message.
|
|
2650
|
+
chatRecorder?.noteSend(id, turn.turnId, actorOf(req), parsed.data.text, seats);
|
|
2651
|
+
if (preface !== null)
|
|
2652
|
+
chatTranscripts?.appendSystem(id, turn.turnId, preface);
|
|
2558
2653
|
}
|
|
2559
2654
|
// crew#641: re-state single-seat degradation on every turn so it is visible in the transcript.
|
|
2560
2655
|
// Decided from `warmRoster` — the seats that are WARM — never from `seats`, which is only the
|
|
@@ -2744,6 +2839,40 @@ runtime = {}) {
|
|
|
2744
2839
|
return reply.send({ output });
|
|
2745
2840
|
return reply.send({ output: null, outputUnavailable: outputUnavailableReason(unit) });
|
|
2746
2841
|
});
|
|
2842
|
+
// DC-S7 (§4.6): what the seats considered, set aside and cited for one chat turn or one unit
|
|
2843
|
+
// attempt. Operator+ (the rules are the project's doctrine). A cited rule is "unchecked", never
|
|
2844
|
+
// "followed" (B4). 404 when the transcript holds no such turn, or the run has no such unit.
|
|
2845
|
+
const operatorOnlyRead = (req, reply) => {
|
|
2846
|
+
const actor = actorOf(req);
|
|
2847
|
+
if (trustAtLeast(actor, 'operator'))
|
|
2848
|
+
return true;
|
|
2849
|
+
void reply.code(403).send({ error: `Insufficient trust: considered needs 'operator' (you are '${actor.trust}')` });
|
|
2850
|
+
return false;
|
|
2851
|
+
};
|
|
2852
|
+
app.get(`${V}/chats/:id/turns/:turnId/considered`, { config: { manifest: { responseType: 'Consideration', statusCodes: [200, 403, 404] } } }, async (req, reply) => {
|
|
2853
|
+
if (!operatorOnlyRead(req, reply))
|
|
2854
|
+
return reply;
|
|
2855
|
+
const { id, turnId } = req.params;
|
|
2856
|
+
const c = await considerations.forChatTurn(id, turnId);
|
|
2857
|
+
if (c === null)
|
|
2858
|
+
return reply.code(404).send({ error: `chat ${id} holds no turn ${turnId}` });
|
|
2859
|
+
return c;
|
|
2860
|
+
});
|
|
2861
|
+
app.get(`${V}/runs/:id/units/:unitKey/considered`, { config: { manifest: { responseType: 'Consideration', statusCodes: [200, 400, 403, 404] } } }, async (req, reply) => {
|
|
2862
|
+
if (!operatorOnlyRead(req, reply))
|
|
2863
|
+
return reply;
|
|
2864
|
+
const { id, unitKey } = req.params;
|
|
2865
|
+
const raw = req.query.attempt;
|
|
2866
|
+
const attemptRaw = Array.isArray(raw) ? raw[0] : raw;
|
|
2867
|
+
const attempt = attemptRaw === undefined || attemptRaw === '' ? 0 : Number(attemptRaw);
|
|
2868
|
+
if (!Number.isInteger(attempt) || attempt < 0) {
|
|
2869
|
+
return reply.code(400).send({ error: '`attempt` must be a non-negative integer' });
|
|
2870
|
+
}
|
|
2871
|
+
const c = await considerations.forUnit(id, unitKey, attempt);
|
|
2872
|
+
if (c === null)
|
|
2873
|
+
return reply.code(404).send({ error: `run ${id} has no unit '${unitKey}'` });
|
|
2874
|
+
return c;
|
|
2875
|
+
});
|
|
2747
2876
|
// The whole run as one auditable JSON attachment: the run, its units (each with
|
|
2748
2877
|
// the captured transcript), and the decision trail read back from core's durable
|
|
2749
2878
|
// per-run event log — what actually happened, not a re-derivation of it.
|
|
@@ -2782,7 +2911,10 @@ runtime = {}) {
|
|
|
2782
2911
|
// catalog entry re-verifies evidence, a registered workflow's def, nothing for free text — and
|
|
2783
2912
|
// an unknown run (or one the daemon cannot read) is declared and denied with the reason.
|
|
2784
2913
|
const verifiedCatalog = typeof adapter.verifiedEvidenceCatalog === 'function' ? await adapter.verifiedEvidenceCatalog() : null;
|
|
2785
|
-
const
|
|
2914
|
+
const knownCatalog = typeof adapter.catalogIds === 'function' ? await adapter.catalogIds() : null;
|
|
2915
|
+
const requirement = acceptanceRequirementOf(run, adapter.listWorkflows(), verifiedCatalog, knownCatalog);
|
|
2916
|
+
// WT-W2: every walkthrough step the requirement names is resolved from its proof root and seal.
|
|
2917
|
+
const walkthroughs = await walkthroughAcceptance(adapter, run, requirement.phases);
|
|
2786
2918
|
let repo = null;
|
|
2787
2919
|
if (run.session.repo_ref !== null) {
|
|
2788
2920
|
const repos = await adapter.listRepos();
|
|
@@ -2799,6 +2931,7 @@ runtime = {}) {
|
|
|
2799
2931
|
// unit both appear on the page humans actually look at.
|
|
2800
2932
|
claims: () => adapter.listConformanceClaims(),
|
|
2801
2933
|
events: (rid) => adapter.runEvents(rid),
|
|
2934
|
+
...(walkthroughs.gates.length > 0 ? { walkthroughs } : {}),
|
|
2802
2935
|
});
|
|
2803
2936
|
});
|
|
2804
2937
|
/**
|
|
@@ -2920,6 +3053,19 @@ runtime = {}) {
|
|
|
2920
3053
|
status,
|
|
2921
3054
|
},
|
|
2922
3055
|
});
|
|
3056
|
+
// DC-S4a host: the gate note (amend / request_changes text) — a bare approve or reject is a
|
|
3057
|
+
// `choice`. Post-commit, off the request path; a non-human actor (a standing order) records nothing.
|
|
3058
|
+
if (decisions !== null) {
|
|
3059
|
+
decisions.track(ingestDecision(decisions, {
|
|
3060
|
+
host: 'gate',
|
|
3061
|
+
actor,
|
|
3062
|
+
words: parsed.data.amend ?? '',
|
|
3063
|
+
choice: parsed.data.action ?? (parsed.data.approve ? 'approve' : 'reject'),
|
|
3064
|
+
projectId: projects.index.projectOf(id) ?? null,
|
|
3065
|
+
runId: id,
|
|
3066
|
+
...(parsed.data.ord !== undefined ? { ord: parsed.data.ord } : {}),
|
|
3067
|
+
}));
|
|
3068
|
+
}
|
|
2923
3069
|
// APPROVE of the steering-author propose gate = the doctrine's landing moment: the
|
|
2924
3070
|
// approved proposal is written to the governance store with `provenance.source: "chat"`,
|
|
2925
3071
|
// audited per rule, idempotent on replay, and LOUD on failure — the response carries the
|
|
@@ -3046,6 +3192,16 @@ runtime = {}) {
|
|
|
3046
3192
|
try {
|
|
3047
3193
|
await adapter.injectWorkerMessage(id, parsed.data.message, parsed.data.target);
|
|
3048
3194
|
audit.record('run.injected', actorOf(req), { runId: id, detail: { target: parsed.data.target } });
|
|
3195
|
+
// DC-S4a host: the operator's mid-run steering text (review S9).
|
|
3196
|
+
if (decisions !== null) {
|
|
3197
|
+
decisions.track(ingestDecision(decisions, {
|
|
3198
|
+
host: 'inject',
|
|
3199
|
+
actor: actorOf(req),
|
|
3200
|
+
words: parsed.data.message,
|
|
3201
|
+
projectId: projects.index.projectOf(id) ?? null,
|
|
3202
|
+
runId: id,
|
|
3203
|
+
}));
|
|
3204
|
+
}
|
|
3049
3205
|
return reply.send({ status: 'ok' });
|
|
3050
3206
|
}
|
|
3051
3207
|
catch (err) {
|
|
@@ -3298,6 +3454,19 @@ runtime = {}) {
|
|
|
3298
3454
|
runId: id,
|
|
3299
3455
|
detail: { elicitationId: taken.entry.elicitationId, action: body.action },
|
|
3300
3456
|
});
|
|
3457
|
+
// DC-S4a host: a free-text answer is words; a picked option (or a decline) is a `choice`.
|
|
3458
|
+
if (decisions !== null) {
|
|
3459
|
+
const picked = taken.entry.options !== null;
|
|
3460
|
+
decisions.track(ingestDecision(decisions, {
|
|
3461
|
+
host: 'elicitation',
|
|
3462
|
+
actor: actorOf(req),
|
|
3463
|
+
words: response !== null && !picked ? response : '',
|
|
3464
|
+
...(response !== null && picked ? { choice: response } : body.action !== 'accept' ? { choice: body.action } : {}),
|
|
3465
|
+
projectId: projects.index.projectOf(id) ?? null,
|
|
3466
|
+
runId: id,
|
|
3467
|
+
elicitationId: taken.entry.elicitationId,
|
|
3468
|
+
}));
|
|
3469
|
+
}
|
|
3301
3470
|
return { status: 'resolved' };
|
|
3302
3471
|
});
|
|
3303
3472
|
// The durable history of one run (FINDING-057).
|
|
@@ -3656,6 +3825,21 @@ runtime = {}) {
|
|
|
3656
3825
|
});
|
|
3657
3826
|
}
|
|
3658
3827
|
}
|
|
3828
|
+
// DC-S3: `targets.project` and `supersedes` shipped with `considerRules` (wicked-core-ts
|
|
3829
|
+
// 0.7.35). An older engine would accept and drop them, so a project rule would land
|
|
3830
|
+
// everywhere: refuse, naming the fields (codex on DC-S3).
|
|
3831
|
+
if (!adapter.projectRulesSupported() && rule !== null && typeof rule === 'object') {
|
|
3832
|
+
const carried = [
|
|
3833
|
+
...(rule.targets?.project !== undefined ? ['targets.project'] : []),
|
|
3834
|
+
...(rule.supersedes !== undefined ? ['supersedes'] : []),
|
|
3835
|
+
];
|
|
3836
|
+
if (carried.length > 0) {
|
|
3837
|
+
return reply.code(501).send({
|
|
3838
|
+
error: `the installed engine would silently drop ${carried.map((f) => `\`${f}\``).join(', ')} ` +
|
|
3839
|
+
'and land the rule everywhere; upgrade wicked-core-ts (>= 0.7.35)',
|
|
3840
|
+
});
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3659
3843
|
await adapter.upsertConformanceRule(rule);
|
|
3660
3844
|
audit.record('governance.rule.upserted', actorOf(req), { detail: { id: rule?.id } });
|
|
3661
3845
|
return { status: 'ok' };
|
|
@@ -4332,6 +4516,26 @@ runtime = {}) {
|
|
|
4332
4516
|
});
|
|
4333
4517
|
}
|
|
4334
4518
|
}
|
|
4519
|
+
// The watch registry's switches (DES-TRIGGER-REGISTRY-001 §4.2, TR-W5a): only `enabled` and
|
|
4520
|
+
// `threshold` of a shipped entry, and `llm`. Turning a watcher off is the one way to make the
|
|
4521
|
+
// platform stop telling the operator something, so only a PERSON may do it — the "human"
|
|
4522
|
+
// predicate decision capture uses; a worker's bearer gets 403. A bad patch is a 400 naming the
|
|
4523
|
+
// key (never a silent drop), and the daemon stamps who changed what and when.
|
|
4524
|
+
let watchNext;
|
|
4525
|
+
if (Object.hasOwn(patch, 'watch')) {
|
|
4526
|
+
const actor = actorOf(req);
|
|
4527
|
+
if (!isHumanOperator(actor)) {
|
|
4528
|
+
return reply.code(403).send({ error: 'Only a person can change what is watched (watch.* settings); a worker or system actor cannot' });
|
|
4529
|
+
}
|
|
4530
|
+
const registry = runtime.watchRegistry?.() ?? null;
|
|
4531
|
+
if (registry === null) {
|
|
4532
|
+
return reply.code(400).send({ error: 'watch: watching is not configured on this daemon' });
|
|
4533
|
+
}
|
|
4534
|
+
const problem = registry.validateSettingsPatch(patch.watch);
|
|
4535
|
+
if (problem !== null)
|
|
4536
|
+
return reply.code(400).send({ error: problem });
|
|
4537
|
+
watchNext = registry.mergeSettings((await adapter.getSettings()).watch, patch.watch, actor.id, Date.now());
|
|
4538
|
+
}
|
|
4335
4539
|
// Only known engine keys and validated `studio.*` keys are persisted. A key that is
|
|
4336
4540
|
// NEITHER is dropped, not refused: request bodies are forward-additive too (DES-STUDIO-001
|
|
4337
4541
|
// §5.1), so an older daemon meeting a newer client's engine key must not fail the whole
|
|
@@ -4364,11 +4568,15 @@ runtime = {}) {
|
|
|
4364
4568
|
for (const key of studioKeys) {
|
|
4365
4569
|
safe[key] = patch[key];
|
|
4366
4570
|
}
|
|
4571
|
+
if (watchNext !== undefined)
|
|
4572
|
+
safe.watch = watchNext;
|
|
4367
4573
|
// `Object.hasOwn`, NOT `k in safe`: `in` walks the prototype chain, so a dropped key named
|
|
4368
4574
|
// `toString` / `valueOf` / `constructor` would test as "kept" and vanish from `ignored` —
|
|
4369
4575
|
// the exact silent drop this route exists to end.
|
|
4370
4576
|
const ignored = Object.keys(patch).filter((k) => !Object.hasOwn(safe, k));
|
|
4371
4577
|
const settings = await adapter.updateSettings(safe);
|
|
4578
|
+
if (watchNext !== undefined)
|
|
4579
|
+
runtime.watchRegistry?.()?.applySettings(settings.watch);
|
|
4372
4580
|
// Re-apply the worker-config root to this process's env (seat sign-in). The engine reads
|
|
4373
4581
|
// WICKED_WORKER_HOME per worker spawn — never cached — so this alone makes the change live
|
|
4374
4582
|
// at the next spawn: no daemon restart, no engine restart.
|
|
@@ -4383,7 +4591,12 @@ runtime = {}) {
|
|
|
4383
4591
|
// `changed` names every persisted key, engine and `studio.*` alike; `ignored` (present only
|
|
4384
4592
|
// when there is one) is where a dropped unknown key stops being invisible.
|
|
4385
4593
|
audit.record('settings.updated', actorOf(req), {
|
|
4386
|
-
detail: {
|
|
4594
|
+
detail: {
|
|
4595
|
+
changed: Object.keys(safe),
|
|
4596
|
+
...(ignored.length > 0 ? { ignored } : {}),
|
|
4597
|
+
// Which watchers were switched or re-thresholded (health reads the stamps; this is the trail).
|
|
4598
|
+
...(watchNext !== undefined ? { watch: patch.watch } : {}),
|
|
4599
|
+
},
|
|
4387
4600
|
});
|
|
4388
4601
|
return { settings };
|
|
4389
4602
|
});
|
|
@@ -4514,6 +4727,46 @@ runtime = {}) {
|
|
|
4514
4727
|
if (editInFlight(id)) {
|
|
4515
4728
|
return reply.code(409).send({ error: `proposal ${id} is already being accepted` });
|
|
4516
4729
|
}
|
|
4730
|
+
// DC-S4a: a proposal THIS ledger filed for a decision lands through `remember()` — the one
|
|
4731
|
+
// landing path, with the human check inside it (§4.5). A payload naming an id the ledger does
|
|
4732
|
+
// not know is an ordinary policy proposal (no ORIGIN claim) and takes the path below.
|
|
4733
|
+
let decisionId = decisions?.deps.ledger.decisionForProposal(id) ?? null;
|
|
4734
|
+
if (decisions !== null && decisionId === null) {
|
|
4735
|
+
// The ledger links a proposal to its decision only once the `offered` outcome is written; a
|
|
4736
|
+
// proposal crew filed but has not linked yet (the publish window, or a crash in it) names a
|
|
4737
|
+
// decision this ledger HOLDS — it must still land through `remember()` (codex on DC-S4a).
|
|
4738
|
+
try {
|
|
4739
|
+
const pending = (await estateTool('proposal.list', { state: 'pending' }));
|
|
4740
|
+
const named = pending.proposals.find((p) => p.id === id)?.payload?.decision?.id;
|
|
4741
|
+
if (typeof named === 'string' && decisions.deps.ledger.record(named) !== null)
|
|
4742
|
+
decisionId = named;
|
|
4743
|
+
}
|
|
4744
|
+
catch (err) {
|
|
4745
|
+
return estateUpstreamError(reply, err);
|
|
4746
|
+
}
|
|
4747
|
+
}
|
|
4748
|
+
if (decisions !== null && decisionId !== null) {
|
|
4749
|
+
try {
|
|
4750
|
+
const landed = await decisions.remember(decisionId, actorOf(req), { how: 'needs-you' });
|
|
4751
|
+
const view = decisions.deps.ledger.view(decisionId);
|
|
4752
|
+
const landing = {
|
|
4753
|
+
outcome: 'landed',
|
|
4754
|
+
ruleId: landed.rule_id,
|
|
4755
|
+
...(view !== null ? { steering_type: view.edits?.steering_type ?? view.derived.steering_type } : {}),
|
|
4756
|
+
...(landed.project !== undefined ? { project: landed.project } : {}),
|
|
4757
|
+
};
|
|
4758
|
+
return {
|
|
4759
|
+
outcome: 'handed_off',
|
|
4760
|
+
payload: { rule: view?.edits?.statement ?? view?.derived.statement ?? '', severity: 'warn', capture: 'decision', decision: { id: decisionId } },
|
|
4761
|
+
landing,
|
|
4762
|
+
};
|
|
4763
|
+
}
|
|
4764
|
+
catch (err) {
|
|
4765
|
+
if (err instanceof DecisionError)
|
|
4766
|
+
return reply.code(err.status).send({ error: err.message });
|
|
4767
|
+
throw err;
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4517
4770
|
let approved;
|
|
4518
4771
|
try {
|
|
4519
4772
|
approved = (await estateTool('proposal.approve', { id }));
|
|
@@ -4561,26 +4814,13 @@ runtime = {}) {
|
|
|
4561
4814
|
if ('error' in built) {
|
|
4562
4815
|
return landFailed(built.error);
|
|
4563
4816
|
}
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
return landFailed(`the store refused the steering rule derived from the approved policy proposal: ${message(err)}`);
|
|
4569
|
-
}
|
|
4570
|
-
audit.record('governance.rule.upserted', actorOf(req), {
|
|
4571
|
-
detail: {
|
|
4572
|
-
id: built.rule.id,
|
|
4573
|
-
source: 'proposal',
|
|
4574
|
-
via: 'proposal-approve',
|
|
4575
|
-
steeringType: built.steeringType,
|
|
4576
|
-
proposalId: id,
|
|
4577
|
-
},
|
|
4817
|
+
// THE landing (probe, upsert, read-back, audit) — shared with the decision landing (DC-S4a).
|
|
4818
|
+
const landing = await landPolicyRule({ adapter, audit }, built, actorOf(req), {
|
|
4819
|
+
via: 'proposal-approve',
|
|
4820
|
+
proposalId: id,
|
|
4578
4821
|
});
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
ruleId: built.rule.id,
|
|
4582
|
-
steering_type: built.steeringType,
|
|
4583
|
-
};
|
|
4822
|
+
if (landing.outcome === 'failed')
|
|
4823
|
+
return landFailed(landing.error ?? 'the landing failed');
|
|
4584
4824
|
return { outcome: 'handed_off', payload: approved.payload, landing };
|
|
4585
4825
|
});
|
|
4586
4826
|
// POST /proposals/:id/reject → proposal.reject → { ok: true }.
|
|
@@ -4594,6 +4834,19 @@ runtime = {}) {
|
|
|
4594
4834
|
if (editInFlight(id.trim())) {
|
|
4595
4835
|
return reply.code(409).send({ error: `proposal ${id.trim()} is being accepted` });
|
|
4596
4836
|
}
|
|
4837
|
+
// DC-S4a B12: rejecting a decision's proposal dismisses the decision too (one object, two places).
|
|
4838
|
+
const rejectedDecision = decisions?.deps.ledger.decisionForProposal(id.trim()) ?? null;
|
|
4839
|
+
if (decisions !== null && rejectedDecision !== null) {
|
|
4840
|
+
try {
|
|
4841
|
+
await decisions.dismiss(rejectedDecision, actorOf(req), 'not-a-rule');
|
|
4842
|
+
return { ok: true };
|
|
4843
|
+
}
|
|
4844
|
+
catch (err) {
|
|
4845
|
+
if (err instanceof DecisionError)
|
|
4846
|
+
return reply.code(err.status).send({ error: err.message });
|
|
4847
|
+
throw err;
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4597
4850
|
try {
|
|
4598
4851
|
return (await estateTool('proposal.reject', { id }));
|
|
4599
4852
|
}
|
|
@@ -4814,6 +5067,8 @@ runtime = {}) {
|
|
|
4814
5067
|
registerCaptureRoutes(app);
|
|
4815
5068
|
// The Demo experience (studio#373): the demo preset's launch, view, files and script edit.
|
|
4816
5069
|
registerDemoRoutes(app, adapter);
|
|
5070
|
+
// WT-W3: the storyline PUT checks the open gate through the same resolution the gate routes use.
|
|
5071
|
+
registerWalkthroughRoutes(app, adapter, { resolveOpenGate, audit });
|
|
4817
5072
|
// ── Presets (DES-TEAMING-002 §8.4, seam C2) — saved phase selections in the engine's store;
|
|
4818
5073
|
// a launch names one via `workflow`, and the ENGINE resolves it.
|
|
4819
5074
|
registerPresetRoutes(app, adapter, security);
|
|
@@ -4959,6 +5214,6 @@ runtime = {}) {
|
|
|
4959
5214
|
pool: interactiveBridges,
|
|
4960
5215
|
log: (m) => app.log.warn(m),
|
|
4961
5216
|
});
|
|
4962
|
-
return { decideGate };
|
|
5217
|
+
return { decideGate, decisions, chatRecorder, considerations };
|
|
4963
5218
|
}
|
|
4964
5219
|
//# sourceMappingURL=routes.js.map
|