@yagni-app/code 0.3.1 → 0.3.3
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/cli.js +13 -0
- package/dist/crashReport.d.ts +12 -0
- package/dist/crashReport.js +28 -1
- package/dist/extension/crashReport.d.ts +18 -0
- package/dist/extension/crashReport.js +35 -2
- package/dist/extension/footer.d.ts +1 -1
- package/dist/extension/hooks.d.ts +111 -0
- package/dist/extension/hooks.js +666 -0
- package/dist/extension/index.d.ts +13 -6
- package/dist/extension/index.js +57 -7
- package/dist/extension/{approvedPrefixes.js → permission/approvedPrefixes.js} +1 -1
- package/dist/extension/permission/dbReadPolicy.d.ts +90 -0
- package/dist/extension/permission/dbReadPolicy.js +227 -0
- package/dist/extension/{execPolicy.js → permission/execPolicy.js} +99 -8
- package/dist/extension/{permission.d.ts → permission/gate.d.ts} +10 -3
- package/dist/extension/{permission.js → permission/gate.js} +156 -9
- package/dist/extension/{guardian.d.ts → permission/guardian.d.ts} +2 -2
- package/dist/extension/{guardian.js → permission/guardian.js} +1 -1
- package/dist/extension/permission/index.d.ts +14 -0
- package/dist/extension/permission/index.js +14 -0
- package/dist/extension/permission/packageManagerPolicy.d.ts +55 -0
- package/dist/extension/permission/packageManagerPolicy.js +170 -0
- package/dist/extension/pipeline/activityFeed.js +19 -5
- package/dist/extension/pipeline/checker.d.ts +99 -0
- package/dist/extension/pipeline/checker.js +238 -0
- package/dist/extension/pipeline/fanout.d.ts +116 -0
- package/dist/extension/pipeline/fanout.js +248 -0
- package/dist/extension/pipeline/fanoutBeats.d.ts +31 -0
- package/dist/extension/pipeline/fanoutBeats.js +86 -0
- package/dist/extension/pipeline/goCommand.d.ts +14 -0
- package/dist/extension/pipeline/goCommand.js +38 -1
- package/dist/extension/pipeline/headlessGo.d.ts +163 -0
- package/dist/extension/pipeline/headlessGo.js +333 -0
- package/dist/extension/pipeline/invocation.d.ts +31 -3
- package/dist/extension/pipeline/invocation.js +37 -3
- package/dist/extension/pipeline/mission.d.ts +55 -0
- package/dist/extension/pipeline/mission.js +70 -0
- package/dist/extension/pipeline/orchestrator.d.ts +48 -3
- package/dist/extension/pipeline/orchestrator.js +450 -9
- package/dist/extension/pipeline/personas.d.ts +16 -1
- package/dist/extension/pipeline/personas.js +118 -7
- package/dist/extension/pipeline/runSession.d.ts +45 -1
- package/dist/extension/pipeline/runState.d.ts +57 -12
- package/dist/extension/pipeline/runState.js +60 -18
- package/dist/extension/pipeline/runner.js +10 -1
- package/dist/extension/pipeline/stages.d.ts +84 -7
- package/dist/extension/pipeline/stages.js +166 -0
- package/dist/extension/pipeline/tierCap.d.ts +32 -0
- package/dist/extension/pipeline/tierCap.js +57 -0
- package/dist/extension/pipeline/types.d.ts +130 -1
- package/dist/extension/pipeline/types.js +17 -0
- package/dist/extension/pipeline/verify.d.ts +86 -3
- package/dist/extension/pipeline/verify.js +175 -6
- package/dist/extension/subagents.js +13 -0
- package/dist/extension/turnLog.d.ts +38 -0
- package/dist/extension/turnLog.js +93 -0
- package/dist/goHeadless.d.ts +75 -0
- package/dist/goHeadless.js +132 -0
- package/dist/paths.d.ts +9 -0
- package/dist/paths.js +12 -0
- package/dist/promptEnrichment.d.ts +1 -1
- package/dist/promptEnrichment.js +1 -1
- package/package.json +2 -2
- /package/dist/extension/{approvedPrefixes.d.ts → permission/approvedPrefixes.d.ts} +0 -0
- /package/dist/extension/{execPolicy.d.ts → permission/execPolicy.d.ts} +0 -0
|
@@ -121,8 +121,8 @@ export { makeAskYagniTool } from "./askYagniTool.js";
|
|
|
121
121
|
export { makeFileTicketTool, makeUpdateTicketStatusTool } from "./ticketTools.js";
|
|
122
122
|
export { makeAskAdvisorTool, registerAdviseCommand } from "./askAdvisorTool.js";
|
|
123
123
|
export { ADVISOR_TIER, DEFAULT_ADVISOR_LIMITS, decideConsult, formatAdvisorSubtotal, makeAdvisorState, } from "./advisor.js";
|
|
124
|
-
export { DEFAULT_GUARDIAN_LIMITS, GUARDIAN_MODEL_TIER, formatGuardianSubtotal, makeGuardianState, resolveGuardianLimits, reviewCommand, } from "./guardian.js";
|
|
125
|
-
export type { GuardianOutcome, GuardianVerdict, GuardianState, GuardianStateHandle, GuardianLimits, ReviewResult, ReviewCommandDeps, } from "./guardian.js";
|
|
124
|
+
export { DEFAULT_GUARDIAN_LIMITS, GUARDIAN_MODEL_TIER, formatGuardianSubtotal, makeGuardianState, resolveGuardianLimits, reviewCommand, } from "./permission/guardian.js";
|
|
125
|
+
export type { GuardianOutcome, GuardianVerdict, GuardianState, GuardianStateHandle, GuardianLimits, ReviewResult, ReviewCommandDeps, } from "./permission/guardian.js";
|
|
126
126
|
export type { Citation, MakeAskYagniToolOptions } from "./askYagniTool.js";
|
|
127
127
|
export { makeReviewBusinessMatchTool } from "./reviewTool.js";
|
|
128
128
|
export type { MakeReviewToolOptions } from "./reviewTool.js";
|
|
@@ -138,6 +138,8 @@ export { runInitPass, runTeamSetup, registerTeamSetupCommand, isFreshWorkspace,
|
|
|
138
138
|
export type { RunInitPassDeps, InitPassOutcome, RunTeamSetupDeps, TeamSetupOutcome, RepoIntake, RepoIntakeFs, EngineeringDraft, DraftTeam, } from "./initPass.js";
|
|
139
139
|
export { isInitDone, markInitDone, initDoneMarkerFile, _setInitDoneHomeForTest } from "./initDone.js";
|
|
140
140
|
export { brandSystemPrompt, YAGNI_IDENTITY, YAGNI_IDENTITY_DRIVER, YAGNI_IDENTITY_ULTRA, BRAND_NAME } from "./branding.js";
|
|
141
|
+
export { loadHooksConfig, makeHookRunner, registerHooks, matchesMatcher, parsePreToolUseOutput, parsePermissionRequestOutput, parseAdditionalContext, parseCompactCancel, HOOK_SUPPORTED_EVENTS, } from "./hooks.js";
|
|
142
|
+
export type { HookEntry, HookGroup, HooksConfig, HookEventName, HookRunner, PreToolUseHookResult, PermissionRequestHookResult, RegisterHooksDeps, } from "./hooks.js";
|
|
141
143
|
export { createUltraHolder, registerUltraCommand } from "./ultra.js";
|
|
142
144
|
export type { UltraHolder } from "./ultra.js";
|
|
143
145
|
export { attributionHeaders, isDriverCaller, fetchCatalog, getToken, getWorkspaceId, resolveBaseUrl, sanitizeCallerSegment, } from "./config.js";
|
|
@@ -147,6 +149,11 @@ export { registerGoCommand } from "./pipeline/goCommand.js";
|
|
|
147
149
|
export type { RegisterGoDeps } from "./pipeline/goCommand.js";
|
|
148
150
|
export { runPipeline } from "./pipeline/orchestrator.js";
|
|
149
151
|
export type { RunPipelineDeps } from "./pipeline/orchestrator.js";
|
|
152
|
+
export { HEADLESS_GO_EXIT, HEADLESS_GO_USAGE, parseHeadlessGoArgs, runHeadlessGo, validateHeadlessGoArgs, } from "./pipeline/headlessGo.js";
|
|
153
|
+
export type { HeadlessGoArgs, HeadlessGoDeps, HeadlessGoOutcome, HeadlessGoResult, HeadlessStageRow, } from "./pipeline/headlessGo.js";
|
|
154
|
+
export { isMissionMode, missionSeed, missionSkippedStages, normalizeMission } from "./pipeline/mission.js";
|
|
155
|
+
export type { MissionInputs } from "./pipeline/mission.js";
|
|
156
|
+
export { clampTier, parseTierCap, resolveTierCap, TIER_CAP_ENV } from "./pipeline/tierCap.js";
|
|
150
157
|
export { withResilience, classifyTransient, composeAbortSignal } from "./pipeline/resilience.js";
|
|
151
158
|
export type { ResilienceAttemptRecord, ResilienceSeams, RunStageFn } from "./pipeline/resilience.js";
|
|
152
159
|
export { DEFAULT_RESILIENCE_POLICY } from "./pipeline/types.js";
|
|
@@ -160,10 +167,10 @@ export { registerSubagents, makeSubagentTool, discoverSubagents, parseAgentMarkd
|
|
|
160
167
|
export type { SubagentDef, SubagentSource } from "./subagents.js";
|
|
161
168
|
export { registerTodos, makeTodoTool, normalizeTodos, reconstructTodos, renderTodoWidget, formatTodoList, formatTodoReminder, shouldRemindTodos, todoSummary, TODO_TOOL_NAME, TODO_REMINDER_TURNS, MAX_TODOS, } from "./todos.js";
|
|
162
169
|
export type { TodoItem, TodoStatus, TodoTheme } from "./todos.js";
|
|
163
|
-
export { decideGate, registerPermissionGate, filterStaleModeContext, filterStalePlanContext, buildModeContextMessage, DEFAULT_PERMISSION_POLICY, MODE_CONTEXT_TYPE, PLAN_CONTEXT_TYPE, PLAN_CONTEXT_MESSAGE, } from "./permission.js";
|
|
164
|
-
export { classifyCommand, DEFAULT_EXEC_POLICY, } from "./execPolicy.js";
|
|
165
|
-
export type { ExecDecision, ExecPolicy, PrefixRule, ExecClassification, } from "./execPolicy.js";
|
|
166
|
-
export type { PermissionMode, PermissionPolicy, GateDecision, RegisterPermissionDeps, BlessRememberInfo, } from "./permission.js";
|
|
170
|
+
export { decideGate, registerPermissionGate, filterStaleModeContext, filterStalePlanContext, buildModeContextMessage, DEFAULT_PERMISSION_POLICY, MODE_CONTEXT_TYPE, PLAN_CONTEXT_TYPE, PLAN_CONTEXT_MESSAGE, } from "./permission/gate.js";
|
|
171
|
+
export { classifyCommand, DEFAULT_EXEC_POLICY, } from "./permission/execPolicy.js";
|
|
172
|
+
export type { ExecDecision, ExecPolicy, PrefixRule, ExecClassification, } from "./permission/execPolicy.js";
|
|
173
|
+
export type { PermissionMode, PermissionPolicy, GateDecision, RegisterPermissionDeps, BlessRememberInfo, } from "./permission/gate.js";
|
|
167
174
|
export { makeBlessStore, blessPath } from "./bless.js";
|
|
168
175
|
export type { BlessStore, BlessRule } from "./bless.js";
|
|
169
176
|
export { bankDecision, fetchDecisions, supersedeDecision, parseDecideArgs, formatDecisionsList, shortId, registerDecisionCommands, DEFAULT_LIST_LIMIT, } from "./decisions.js";
|
package/dist/extension/index.js
CHANGED
|
@@ -3,9 +3,9 @@ import { appendFileSync, mkdirSync } from "node:fs";
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { Text } from "@earendil-works/pi-tui";
|
|
5
5
|
import { DEFAULT_ADVISOR_LIMITS, formatAdvisorSubtotal, makeAdvisorState } from "./advisor.js";
|
|
6
|
-
import { appendGrant, loadGrants, resolveRepoKey, storagePrefix } from "./approvedPrefixes.js";
|
|
6
|
+
import { appendGrant, loadGrants, resolveRepoKey, storagePrefix } from "./permission/approvedPrefixes.js";
|
|
7
7
|
import { redactCommand } from "./redact.js";
|
|
8
|
-
import { formatGuardianSubtotal, GUARDIAN_MODEL_TIER, makeGuardianState, resolveGuardianLimits, reviewCommand } from "./guardian.js";
|
|
8
|
+
import { formatGuardianSubtotal, GUARDIAN_MODEL_TIER, makeGuardianState, resolveGuardianLimits, reviewCommand } from "./permission/guardian.js";
|
|
9
9
|
import { makeAskAdvisorTool, registerAdviseCommand } from "./askAdvisorTool.js";
|
|
10
10
|
import { makeAskYagniTool } from "./askYagniTool.js";
|
|
11
11
|
import { makeFileTicketTool, makeUpdateTicketStatusTool } from "./ticketTools.js";
|
|
@@ -20,6 +20,7 @@ import { registerCostCommand } from "./costHud.js";
|
|
|
20
20
|
import { isDebug } from "./diagnostics.js";
|
|
21
21
|
import { droppedSessionRuns, sessionRunIds } from "./sessionRuns.js";
|
|
22
22
|
import { codeStateHome } from "./stateHome.js";
|
|
23
|
+
import { logTurnLifecycle } from "./turnLog.js";
|
|
23
24
|
import { createYagniFooterFactory, cyclePermissionMode, formatCwd, GIT_MUTATING_PATTERN, isShiftTab } from "./footer.js";
|
|
24
25
|
import { RerouteNotifier } from "./rerouteNotice.js";
|
|
25
26
|
import { isFreshWorkspace, registerTeamSetupCommand, runInitPass as defaultRunInitPass } from "./initPass.js";
|
|
@@ -27,7 +28,8 @@ import { isInitDone as defaultIsInitDone, markInitDone as defaultMarkInitDone }
|
|
|
27
28
|
import { fetchMcpServers as defaultFetchMcpServers, registerMcpCommand, registerMcpTools, } from "./mcpTools.js";
|
|
28
29
|
import { registerGoCommand } from "./pipeline/goCommand.js";
|
|
29
30
|
import { registerGoCompareCommand } from "./pipeline/goCompareCommand.js";
|
|
30
|
-
import { DEFAULT_PERMISSION_POLICY, createModeHolder, registerPermissionGate } from "./permission.js";
|
|
31
|
+
import { DEFAULT_PERMISSION_POLICY, createModeHolder, registerPermissionGate } from "./permission/gate.js";
|
|
32
|
+
import { loadHooksConfig, makeHookRunner, registerHooks } from "./hooks.js";
|
|
31
33
|
import { registerSubagents } from "./subagents.js";
|
|
32
34
|
import { createUltraHolder, registerUltraCommand } from "./ultra.js";
|
|
33
35
|
import { registerTodos } from "./todos.js";
|
|
@@ -139,7 +141,12 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
139
141
|
// `yagni` provider, so /model and Ctrl+P show a single entry. Child
|
|
140
142
|
// processes (/go, subagents, advisor) fetch their own catalog and register
|
|
141
143
|
// their own provider, so they are unaffected by this filter.
|
|
142
|
-
|
|
144
|
+
//
|
|
145
|
+
// Eval mode is exempt: a headless harness (scoping sessions, code evals)
|
|
146
|
+
// names its tier explicitly — the backend's scoping sessions run `--model
|
|
147
|
+
// standard` — and there is no /model picker to keep tidy. Filtering there
|
|
148
|
+
// turns a valid tier request into "no models match".
|
|
149
|
+
const catalog = evalMode ? fullCatalog : fullCatalog.filter((m) => m.id === "advanced");
|
|
143
150
|
// YAG-471: the driver's own completions carry attribution headers read from
|
|
144
151
|
// this process's env (YAGNI_SESSION_ID minted by the launcher; YAGNI_CALLER
|
|
145
152
|
// defaults to "driver" when unset, i.e. every session that is not a /go
|
|
@@ -206,6 +213,10 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
206
213
|
// The grounded multi-agent pipeline entry point: /go <ticket> runs
|
|
207
214
|
// map → plan → implement → review → fix, each child grounded by inheritance.
|
|
208
215
|
registerGoCommand(pi, {
|
|
216
|
+
// /ultra is one dial for the whole session: the same holder the subagent
|
|
217
|
+
// tool reads widens the implement diamond's parallel ceiling (4 -> 8) for
|
|
218
|
+
// the fan and its fix turns. Read per run, so a toggle lands on the next /go.
|
|
219
|
+
isUltra: () => ultraHolder.get(),
|
|
209
220
|
// Task 8: /go's end-of-run summary prefers the server-priced per-stage
|
|
210
221
|
// breakdown for the ONE run that just finished (`?runId=`, aliasing the
|
|
211
222
|
// spend endpoint's `sessionId` param — see the backend route), over
|
|
@@ -280,6 +291,12 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
280
291
|
// rejected as a same-session self-authorization path, PR #1698).
|
|
281
292
|
const sessionGrants = evalMode ? [] : loadGrants();
|
|
282
293
|
const GUARDIAN_EVENT_TIMEOUT_MS = 5_000;
|
|
294
|
+
// YAG-506: load user-configurable lifecycle hooks config and create the
|
|
295
|
+
// hook runner for the permission gate. Skipped in eval mode.
|
|
296
|
+
const hooksConfig = evalMode ? {} : loadHooksConfig();
|
|
297
|
+
const hookRunner = evalMode ? null : makeHookRunner({ config: hooksConfig });
|
|
298
|
+
if (!evalMode)
|
|
299
|
+
registerHooks(pi, { config: hooksConfig });
|
|
283
300
|
registerPermissionGate(pi, {
|
|
284
301
|
modeHolder,
|
|
285
302
|
guardianState,
|
|
@@ -362,6 +379,7 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
362
379
|
onBlessRemember: decisionCapture
|
|
363
380
|
? (ctx, info) => decisionCapture.captureFromBless(ctx, info)
|
|
364
381
|
: undefined,
|
|
382
|
+
...(hookRunner ? { hookRunner } : {}),
|
|
365
383
|
});
|
|
366
384
|
// P2/YAG-383: /cost prefers the server-authoritative session spend (covers
|
|
367
385
|
// subagents and advisor consults directly, since they bill under this same
|
|
@@ -564,6 +582,28 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
564
582
|
rulesSection,
|
|
565
583
|
}),
|
|
566
584
|
});
|
|
585
|
+
// Turn-lifecycle WAL: a `turn_start` with no matching `turn_end` is the
|
|
586
|
+
// silent-freeze tell (the turn that left the UI stuck on "Working…"), even
|
|
587
|
+
// if the process is later killed. `turn_end` records the final stopReason so
|
|
588
|
+
// an `error` / `aborted` end is visible at a glance without reading the raw
|
|
589
|
+
// session .jsonl. Fail-soft; nothing here may break a turn.
|
|
590
|
+
const turnStartMs = new Map();
|
|
591
|
+
const sessionIdForLog = () => deps.env?.YAGNI_SESSION_ID;
|
|
592
|
+
pi.on("turn_start", (event) => {
|
|
593
|
+
turnStartMs.set(event.turnIndex, Date.now());
|
|
594
|
+
logTurnLifecycle({ kind: "turn_start", sessionId: sessionIdForLog(), turnIndex: event.turnIndex });
|
|
595
|
+
});
|
|
596
|
+
pi.on("turn_end", (event) => {
|
|
597
|
+
const startedAt = turnStartMs.get(event.turnIndex);
|
|
598
|
+
turnStartMs.delete(event.turnIndex);
|
|
599
|
+
logTurnLifecycle({
|
|
600
|
+
kind: "turn_end",
|
|
601
|
+
sessionId: sessionIdForLog(),
|
|
602
|
+
turnIndex: event.turnIndex,
|
|
603
|
+
stopReason: event.message?.stopReason,
|
|
604
|
+
elapsedMs: startedAt !== undefined ? Date.now() - startedAt : undefined,
|
|
605
|
+
});
|
|
606
|
+
});
|
|
567
607
|
// Two finalized-message guards share this handler (their conditions are
|
|
568
608
|
// mutually exclusive: YAG-460 takes error-stopped messages, YAG-466 takes
|
|
569
609
|
// stop/length ones without an errorMessage).
|
|
@@ -847,7 +887,7 @@ export { makeAskYagniTool } from "./askYagniTool.js";
|
|
|
847
887
|
export { makeFileTicketTool, makeUpdateTicketStatusTool } from "./ticketTools.js";
|
|
848
888
|
export { makeAskAdvisorTool, registerAdviseCommand } from "./askAdvisorTool.js";
|
|
849
889
|
export { ADVISOR_TIER, DEFAULT_ADVISOR_LIMITS, decideConsult, formatAdvisorSubtotal, makeAdvisorState, } from "./advisor.js";
|
|
850
|
-
export { DEFAULT_GUARDIAN_LIMITS, GUARDIAN_MODEL_TIER, formatGuardianSubtotal, makeGuardianState, resolveGuardianLimits, reviewCommand, } from "./guardian.js";
|
|
890
|
+
export { DEFAULT_GUARDIAN_LIMITS, GUARDIAN_MODEL_TIER, formatGuardianSubtotal, makeGuardianState, resolveGuardianLimits, reviewCommand, } from "./permission/guardian.js";
|
|
851
891
|
export { makeReviewBusinessMatchTool } from "./reviewTool.js";
|
|
852
892
|
export { makeRecordEngineeringContextTool } from "./recordContextTool.js";
|
|
853
893
|
export { makeRecordDecisionTool } from "./recordDecisionTool.js";
|
|
@@ -859,12 +899,22 @@ export { runInitPass, runTeamSetup, registerTeamSetupCommand, isFreshWorkspace,
|
|
|
859
899
|
// Onramp Door B (F2a): the one-time init-pass idempotency marker.
|
|
860
900
|
export { isInitDone, markInitDone, initDoneMarkerFile, _setInitDoneHomeForTest } from "./initDone.js";
|
|
861
901
|
export { brandSystemPrompt, YAGNI_IDENTITY, YAGNI_IDENTITY_DRIVER, YAGNI_IDENTITY_ULTRA, BRAND_NAME } from "./branding.js";
|
|
902
|
+
// YAG-506: user-configurable lifecycle hooks.
|
|
903
|
+
export { loadHooksConfig, makeHookRunner, registerHooks, matchesMatcher, parsePreToolUseOutput, parsePermissionRequestOutput, parseAdditionalContext, parseCompactCancel, HOOK_SUPPORTED_EVENTS, } from "./hooks.js";
|
|
862
904
|
// Ultra mode (/ultra): the aggressive fan-out/verify/synthesize dial.
|
|
863
905
|
export { createUltraHolder, registerUltraCommand } from "./ultra.js";
|
|
864
906
|
export { attributionHeaders, isDriverCaller, fetchCatalog, getToken, getWorkspaceId, resolveBaseUrl, sanitizeCallerSegment, } from "./config.js";
|
|
865
907
|
export { buildYagniProvider } from "./provider.js";
|
|
866
908
|
export { registerGoCommand } from "./pipeline/goCommand.js";
|
|
867
909
|
export { runPipeline } from "./pipeline/orchestrator.js";
|
|
910
|
+
// The headless front door (`yagni go --headless`): the same pipeline, driven by
|
|
911
|
+
// a script or the mission sandbox instead of a session.
|
|
912
|
+
export { HEADLESS_GO_EXIT, HEADLESS_GO_USAGE, parseHeadlessGoArgs, runHeadlessGo, validateHeadlessGoArgs, } from "./pipeline/headlessGo.js";
|
|
913
|
+
// Mission mode: the pure rules for entering the pipeline on an already-approved
|
|
914
|
+
// plan (map/plan skipped, memo as the stand-in repo brief, FINISH not ours).
|
|
915
|
+
export { isMissionMode, missionSeed, missionSkippedStages, normalizeMission } from "./pipeline/mission.js";
|
|
916
|
+
// The eval/smoke tier ceiling (YAGNI_GO_TIER_CAP), clamped centrally in runStage.
|
|
917
|
+
export { clampTier, parseTierCap, resolveTierCap, TIER_CAP_ENV } from "./pipeline/tierCap.js";
|
|
868
918
|
// R1: the in-loop resilience HOF a dev can compose with (or replace at) the
|
|
869
919
|
// `runStage` seam, plus its policy type and the default policy.
|
|
870
920
|
export { withResilience, classifyTransient, composeAbortSignal } from "./pipeline/resilience.js";
|
|
@@ -880,8 +930,8 @@ export { registerSubagents, makeSubagentTool, discoverSubagents, parseAgentMarkd
|
|
|
880
930
|
export { registerTodos, makeTodoTool, normalizeTodos, reconstructTodos, renderTodoWidget, formatTodoList, formatTodoReminder, shouldRemindTodos, todoSummary, TODO_TOOL_NAME, TODO_REMINDER_TURNS, MAX_TODOS, } from "./todos.js";
|
|
881
931
|
// P3 + W4: the permission gate seam (decideGate is pure; policy injectable) plus
|
|
882
932
|
// the session bless-with-remember capture hook.
|
|
883
|
-
export { decideGate, registerPermissionGate, filterStaleModeContext, filterStalePlanContext, buildModeContextMessage, DEFAULT_PERMISSION_POLICY, MODE_CONTEXT_TYPE, PLAN_CONTEXT_TYPE, PLAN_CONTEXT_MESSAGE, } from "./permission.js";
|
|
884
|
-
export { classifyCommand, DEFAULT_EXEC_POLICY, } from "./execPolicy.js";
|
|
933
|
+
export { decideGate, registerPermissionGate, filterStaleModeContext, filterStalePlanContext, buildModeContextMessage, DEFAULT_PERMISSION_POLICY, MODE_CONTEXT_TYPE, PLAN_CONTEXT_TYPE, PLAN_CONTEXT_MESSAGE, } from "./permission/gate.js";
|
|
934
|
+
export { classifyCommand, DEFAULT_EXEC_POLICY, } from "./permission/execPolicy.js";
|
|
885
935
|
// W4 judgment loop: the session bless store (tool + path-prefix, session-only).
|
|
886
936
|
export { makeBlessStore, blessPath } from "./bless.js";
|
|
887
937
|
// W4 judgment loop: the decisions surface (/decide + /decisions) + shared bank.
|
|
@@ -29,7 +29,7 @@ import { execFileSync } from "node:child_process";
|
|
|
29
29
|
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
30
30
|
import { dirname, join } from "node:path";
|
|
31
31
|
import { classifyCommand, shellParse, tokenize } from "./execPolicy.js";
|
|
32
|
-
import { codeStateHome } from "
|
|
32
|
+
import { codeStateHome } from "../stateHome.js";
|
|
33
33
|
// --- Derivation ---
|
|
34
34
|
/** Tools whose second token is a subcommand worth capturing in a prefix. */
|
|
35
35
|
const MULTI_SUBCOMMAND_TOOLS = new Set([
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database read auto-allow policy (YAG-561).
|
|
3
|
+
*
|
|
4
|
+
* Auto-allows *provably read-only* inline SQL for a small set of database
|
|
5
|
+
* clients, so routine SELECTs never reach the Guardian. Anything we cannot
|
|
6
|
+
* prove read-only is left ENTIRELY to the Guardian — this module has no
|
|
7
|
+
* "deny" outcome (see the invariant below).
|
|
8
|
+
*
|
|
9
|
+
* The hard, easy part is extracting the SQL safely WITHOUT regex over the raw
|
|
10
|
+
* command. We reuse execPolicy's shellParse — the same quote-aware tokenizer
|
|
11
|
+
* that powers the whole classifier — so `-c "SELECT 1; SELECT 2"` arrives as
|
|
12
|
+
* one clean, dequoted token and a `;` OUTSIDE quotes still reads as a real
|
|
13
|
+
* command separator. Extraction failures (unknown flag, unknown short-cluster,
|
|
14
|
+
* `-f file`, stdin `<`) simply left as `readonly: false`.
|
|
15
|
+
*
|
|
16
|
+
* ── The single invariant ────────────────────────────────────────────────
|
|
17
|
+
* classifySql returns "read" ONLY when the SQL is provably read-only. A
|
|
18
|
+
* false "read" auto-runs a write across every workspace's DB, which is
|
|
19
|
+
* unacceptable; a false "unknown" costs one Guardian consult, which is free.
|
|
20
|
+
* We therefore over-reject: a write marker, a mutating function name, or a
|
|
21
|
+
* `\` meta-command ANYWHERE in the SQL downgrades the whole thing to
|
|
22
|
+
* "unknown". No attempt is made to distinguish "called" from "mentioned in
|
|
23
|
+
* a string literal" — erring toward prompt is always the safe direction.
|
|
24
|
+
*
|
|
25
|
+
* NOTE: a non-"read" result is NOT "forbidden". An UPDATE/INSERT/DELETE is
|
|
26
|
+
* sent to the Guardian where a human/LLM judges intent. This module never
|
|
27
|
+
* flat-denies a DB write.
|
|
28
|
+
*
|
|
29
|
+
* ── How to extend ────────────────────────────────────────────────────────
|
|
30
|
+
* Add a new client: push a DbClientSpec to DB_CLIENTS.
|
|
31
|
+
* Add a new mutating function: append to the relevant client's
|
|
32
|
+
* mutatingFunctions list (schema-prefix-agnostic word-boundary match).
|
|
33
|
+
* Add a new way to pass SQL: add a SqlArgExtractor to the client.
|
|
34
|
+
* Every change needs a paired allow + regression test in dbReadPolicy.test.ts.
|
|
35
|
+
* See AGENTS.md alongside this directory for the full recipe.
|
|
36
|
+
*/
|
|
37
|
+
/** A single argv shape that carries inline SQL (e.g. psql `-c "…"`). */
|
|
38
|
+
export interface SqlArgExtractor {
|
|
39
|
+
/**
|
|
40
|
+
* Token forms introducing SQL: `-c`, `--command`, `-e`, `--execute`, … The
|
|
41
|
+
* extractor accepts these in three forms: glued long (`--command=…`), glued
|
|
42
|
+
* short (`-c…` for the 2-char flags only), and bare (`-c` → SQL is the next
|
|
43
|
+
* token). A bare single-dash flag may also match a short cluster
|
|
44
|
+
* (`-Atc`) when its terminal letter is `shortCluster.terminal` and every
|
|
45
|
+
* preceding letter is in `shortCluster.allowedPrefixes`.
|
|
46
|
+
*/
|
|
47
|
+
flags: readonly string[];
|
|
48
|
+
shortCluster?: {
|
|
49
|
+
terminal: string;
|
|
50
|
+
allowedPrefixes: readonly string[];
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** Everything we know about one database client. */
|
|
54
|
+
export interface DbClientSpec {
|
|
55
|
+
binaries: readonly string[];
|
|
56
|
+
sqlArgExtractors: readonly SqlArgExtractor[];
|
|
57
|
+
/** Is this (dequoted, full) SQL provably read-only? */
|
|
58
|
+
classifySql: (sql: string) => boolean;
|
|
59
|
+
/** Function names that make a bare SELECT mutate/abuse — reject as unknown. */
|
|
60
|
+
mutatingFunctions: readonly string[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The shared "provably read-only" gate. Returns true only when every
|
|
64
|
+
* statement in `sql` (after comment-stripping) begins with SELECT/WITH and
|
|
65
|
+
* contains no write verb, write clause, mutating function, or backslash
|
|
66
|
+
* meta-command.
|
|
67
|
+
*/
|
|
68
|
+
export declare function isSqlReadOnly(sql: string, mutatingFunctions: readonly string[]): boolean;
|
|
69
|
+
/** The supported database clients. Add a new one here to expand coverage. */
|
|
70
|
+
export declare const DB_CLIENTS: DbClientSpec[];
|
|
71
|
+
/**
|
|
72
|
+
* Resolve the SQL string for a known client binary from its tokenized argv.
|
|
73
|
+
* Returns `{ sql }` on success (the dequoted SQL as one string) or null when
|
|
74
|
+
* the argv shape can't be proven to carry inline SQL. Null → left to Guardian.
|
|
75
|
+
*
|
|
76
|
+
* Supported token shapes, in the order tried:
|
|
77
|
+
* 1. glued long flag `--command=SELECT …` / `--execute=SELECT …`
|
|
78
|
+
* 2. glued short flag `-cSELECT …` / `-eSELECT …` (2-char flag + payload)
|
|
79
|
+
* 3. bare flag `-c` / `--command` / `-Atc` → SQL is the NEXT token
|
|
80
|
+
*/
|
|
81
|
+
export declare function extractSqlArg(tokens: readonly string[], spec: DbClientSpec): {
|
|
82
|
+
sql: string;
|
|
83
|
+
} | null;
|
|
84
|
+
/**
|
|
85
|
+
* Top-level: should this database command be auto-allowed? Consumes the raw
|
|
86
|
+
* tokenized argv (tokens[0] is the binary) and returns true ONLY when the
|
|
87
|
+
* inline SQL was extracted AND classified read-only.
|
|
88
|
+
*/
|
|
89
|
+
export declare function classifyDbRead(tokens: readonly string[]): boolean;
|
|
90
|
+
//# sourceMappingURL=dbReadPolicy.d.ts.map
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database read auto-allow policy (YAG-561).
|
|
3
|
+
*
|
|
4
|
+
* Auto-allows *provably read-only* inline SQL for a small set of database
|
|
5
|
+
* clients, so routine SELECTs never reach the Guardian. Anything we cannot
|
|
6
|
+
* prove read-only is left ENTIRELY to the Guardian — this module has no
|
|
7
|
+
* "deny" outcome (see the invariant below).
|
|
8
|
+
*
|
|
9
|
+
* The hard, easy part is extracting the SQL safely WITHOUT regex over the raw
|
|
10
|
+
* command. We reuse execPolicy's shellParse — the same quote-aware tokenizer
|
|
11
|
+
* that powers the whole classifier — so `-c "SELECT 1; SELECT 2"` arrives as
|
|
12
|
+
* one clean, dequoted token and a `;` OUTSIDE quotes still reads as a real
|
|
13
|
+
* command separator. Extraction failures (unknown flag, unknown short-cluster,
|
|
14
|
+
* `-f file`, stdin `<`) simply left as `readonly: false`.
|
|
15
|
+
*
|
|
16
|
+
* ── The single invariant ────────────────────────────────────────────────
|
|
17
|
+
* classifySql returns "read" ONLY when the SQL is provably read-only. A
|
|
18
|
+
* false "read" auto-runs a write across every workspace's DB, which is
|
|
19
|
+
* unacceptable; a false "unknown" costs one Guardian consult, which is free.
|
|
20
|
+
* We therefore over-reject: a write marker, a mutating function name, or a
|
|
21
|
+
* `\` meta-command ANYWHERE in the SQL downgrades the whole thing to
|
|
22
|
+
* "unknown". No attempt is made to distinguish "called" from "mentioned in
|
|
23
|
+
* a string literal" — erring toward prompt is always the safe direction.
|
|
24
|
+
*
|
|
25
|
+
* NOTE: a non-"read" result is NOT "forbidden". An UPDATE/INSERT/DELETE is
|
|
26
|
+
* sent to the Guardian where a human/LLM judges intent. This module never
|
|
27
|
+
* flat-denies a DB write.
|
|
28
|
+
*
|
|
29
|
+
* ── How to extend ────────────────────────────────────────────────────────
|
|
30
|
+
* Add a new client: push a DbClientSpec to DB_CLIENTS.
|
|
31
|
+
* Add a new mutating function: append to the relevant client's
|
|
32
|
+
* mutatingFunctions list (schema-prefix-agnostic word-boundary match).
|
|
33
|
+
* Add a new way to pass SQL: add a SqlArgExtractor to the client.
|
|
34
|
+
* Every change needs a paired allow + regression test in dbReadPolicy.test.ts.
|
|
35
|
+
* See AGENTS.md alongside this directory for the full recipe.
|
|
36
|
+
*/
|
|
37
|
+
// --- Read-only classifier helpers ---
|
|
38
|
+
/** Strip leading/leading whitespace and `/* … */` block + `-- …` line comments. */
|
|
39
|
+
function stripSqlComments(sql) {
|
|
40
|
+
let out = sql;
|
|
41
|
+
// Block comments (non-greedy, across newlines).
|
|
42
|
+
out = out.replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
43
|
+
// Line comments to end-of-line.
|
|
44
|
+
out = out.replace(/--[^\n]*/g, " ");
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
/** Write verbs / statements that are never read-only. */
|
|
48
|
+
const WRITE_VERBS = /\b(insert|update|delete|truncate|drop|create|alter|grant|revoke|copy|vacuum|merge|call|set|replace|load)\b/i;
|
|
49
|
+
/** Statements that use SELECT but still write (INTO, FOR UPDATE, …). */
|
|
50
|
+
const WRITE_CLAUSES = /\b(into\s+(outfile|dumpfile|new\s+table|table)|for\s+(update|share)|returning)\b/i;
|
|
51
|
+
/** Match a function name with a word boundary (catches `schema.fn` and `fn(`). */
|
|
52
|
+
function mentionsFunction(sql, fn) {
|
|
53
|
+
const re = new RegExp(`(^|[^\\w])${fn}\\s*\\(`, "i");
|
|
54
|
+
return re.test(sql);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The shared "provably read-only" gate. Returns true only when every
|
|
58
|
+
* statement in `sql` (after comment-stripping) begins with SELECT/WITH and
|
|
59
|
+
* contains no write verb, write clause, mutating function, or backslash
|
|
60
|
+
* meta-command.
|
|
61
|
+
*/
|
|
62
|
+
export function isSqlReadOnly(sql, mutatingFunctions) {
|
|
63
|
+
const stripped = stripSqlComments(sql).trim();
|
|
64
|
+
// Any backslash is a psql/mysql meta-command (`\copy`, `\!`, `\i`) — not a
|
|
65
|
+
// plain SQL statement. Never auto-allow.
|
|
66
|
+
if (stripped.includes("\\"))
|
|
67
|
+
return false;
|
|
68
|
+
// A mutating function ANYWHERE (schema-prefixed, string-literal mention, or
|
|
69
|
+
// genuine call) downgrades the whole thing. Over-reject on purpose.
|
|
70
|
+
if (mutatingFunctions.some((fn) => mentionsFunction(stripped, fn)))
|
|
71
|
+
return false;
|
|
72
|
+
// Write verbs / clauses anywhere → not read-only. (Single pass over the whole
|
|
73
|
+
// multi-statement string means any `;`-joined write poisons the read.)
|
|
74
|
+
if (WRITE_VERBS.test(stripped))
|
|
75
|
+
return false;
|
|
76
|
+
if (WRITE_CLAUSES.test(stripped))
|
|
77
|
+
return false;
|
|
78
|
+
// The first statement must open with SELECT or WITH. (A leading `WITH`
|
|
79
|
+
// CTE is safe, but `WITH` can also be INSERT … WITH — the WRITE_VERBS
|
|
80
|
+
// check already caught any write verb, so a remaining `WITH` lead is a CTE.)
|
|
81
|
+
if (!/^(select|with)\b/i.test(stripped))
|
|
82
|
+
return false;
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
// --- Client registry ---
|
|
86
|
+
/**
|
|
87
|
+
* PostgreSQL. Inline SQL via `-c` / `--command` (glued or `=`) and the
|
|
88
|
+
* `-Atc`-style short cluster. Mutating built-ins that a plain SELECT can
|
|
89
|
+
* smuggle are rejected as unknown.
|
|
90
|
+
*/
|
|
91
|
+
const postgresMutatingFunctions = [
|
|
92
|
+
// sequence / identity
|
|
93
|
+
"nextval", "setval",
|
|
94
|
+
// large objects (server filesystem writes)
|
|
95
|
+
"lo_import", "lo_export", "lo_creat", "lo_create", "lo_unlink", "lo_put",
|
|
96
|
+
// server admin / signaling
|
|
97
|
+
"pg_terminate_backend", "pg_cancel_backend", "pg_reload_conf",
|
|
98
|
+
"pg_rotate_logfile", "pg_promote", "pg_log_backend_memory_contexts",
|
|
99
|
+
// advisory locks / notification / snapshots (state-changing)
|
|
100
|
+
"pg_advisory_lock", "pg_advisory_lock_shared", "pg_advisory_xact_lock",
|
|
101
|
+
"pg_advisory_xact_lock_shared", "pg_try_advisory_lock",
|
|
102
|
+
"pg_try_advisory_lock_shared", "pg_try_advisory_xact_lock",
|
|
103
|
+
"pg_try_advisory_xact_lock_shared", "pg_advisory_unlock",
|
|
104
|
+
"pg_advisory_unlock_shared", "pg_advisory_unlock_all", "pg_notify",
|
|
105
|
+
"pg_export_snapshot",
|
|
106
|
+
// replication slots
|
|
107
|
+
"pg_create_logical_replication_slot", "pg_drop_replication_slot",
|
|
108
|
+
"pg_create_physical_replication_slot",
|
|
109
|
+
// superuser server-side filesystem reads (exfil surface)
|
|
110
|
+
"pg_read_file", "pg_read_binary_file", "pg_ls_dir", "pg_stat_file",
|
|
111
|
+
// resource abuse
|
|
112
|
+
"pg_sleep", "pg_sleep_for", "pg_sleep_until",
|
|
113
|
+
// remote execution via dblink
|
|
114
|
+
"dblink", "dblink_exec", "dblink_connect", "dblink_disconnect",
|
|
115
|
+
"dblink_send_query", "dblink_get_result", "dblink_open", "dblink_fetch",
|
|
116
|
+
];
|
|
117
|
+
const mysqlMutatingFunctions = [
|
|
118
|
+
"sleep", "benchmark", "load_file",
|
|
119
|
+
"get_lock", "release_lock", "release_all_locks",
|
|
120
|
+
];
|
|
121
|
+
/** The supported database clients. Add a new one here to expand coverage. */
|
|
122
|
+
export const DB_CLIENTS = [
|
|
123
|
+
{
|
|
124
|
+
binaries: ["psql"],
|
|
125
|
+
sqlArgExtractors: [
|
|
126
|
+
{
|
|
127
|
+
flags: ["-c", "--command"],
|
|
128
|
+
shortCluster: {
|
|
129
|
+
terminal: "c",
|
|
130
|
+
allowedPrefixes: ["A", "t", "P", "q", "X", "a", "b", "e", "E", "f", "h", "n", "o", "p", "s", "S", "T", "U", "v", "V", "w", "W", "x", "z"],
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
classifySql: (sql) => isSqlReadOnly(sql, postgresMutatingFunctions),
|
|
135
|
+
mutatingFunctions: postgresMutatingFunctions,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
binaries: ["mysql", "mariadb"],
|
|
139
|
+
sqlArgExtractors: [
|
|
140
|
+
{ flags: ["-e", "--execute"] },
|
|
141
|
+
],
|
|
142
|
+
classifySql: (sql) => isSqlReadOnly(sql, mysqlMutatingFunctions),
|
|
143
|
+
mutatingFunctions: mysqlMutatingFunctions,
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
// --- SQL extraction (reuses shellParse — no regex over the raw command) ---
|
|
147
|
+
/**
|
|
148
|
+
* Does the flag (exact equality) match a token, including a single-dash
|
|
149
|
+
* short-cluster form like `-Atc` (terminal flag letter + an allowlisted set of
|
|
150
|
+
* prefix letters)? Only used when the token has no glued SQL payload.
|
|
151
|
+
*/
|
|
152
|
+
function matchesFlagExactly(token, extractor) {
|
|
153
|
+
if (extractor.flags.includes(token))
|
|
154
|
+
return true;
|
|
155
|
+
if (extractor.shortCluster && token.startsWith("-") && !token.startsWith("--")) {
|
|
156
|
+
const { terminal, allowedPrefixes } = extractor.shortCluster;
|
|
157
|
+
const body = token.slice(1); // drop the leading '-'
|
|
158
|
+
if (!body.endsWith(terminal) || body.length < 2)
|
|
159
|
+
return false;
|
|
160
|
+
const prefixes = body.slice(0, -1).split("");
|
|
161
|
+
return prefixes.every((p) => allowedPrefixes.includes(p));
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Resolve the SQL string for a known client binary from its tokenized argv.
|
|
167
|
+
* Returns `{ sql }` on success (the dequoted SQL as one string) or null when
|
|
168
|
+
* the argv shape can't be proven to carry inline SQL. Null → left to Guardian.
|
|
169
|
+
*
|
|
170
|
+
* Supported token shapes, in the order tried:
|
|
171
|
+
* 1. glued long flag `--command=SELECT …` / `--execute=SELECT …`
|
|
172
|
+
* 2. glued short flag `-cSELECT …` / `-eSELECT …` (2-char flag + payload)
|
|
173
|
+
* 3. bare flag `-c` / `--command` / `-Atc` → SQL is the NEXT token
|
|
174
|
+
*/
|
|
175
|
+
export function extractSqlArg(tokens, spec) {
|
|
176
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
177
|
+
const tok = tokens[i];
|
|
178
|
+
for (const extractor of spec.sqlArgExtractors) {
|
|
179
|
+
// 1. glued long flag: `--command=SELECT …`
|
|
180
|
+
for (const f of extractor.flags) {
|
|
181
|
+
if (f.startsWith("--") && tok.startsWith(f + "=")) {
|
|
182
|
+
const sql = tok.slice(f.length + 1);
|
|
183
|
+
if (sql.length > 0)
|
|
184
|
+
return { sql };
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// 2. glued short flag: `-cSELECT …` (only for bare 2-char flags in the
|
|
189
|
+
// extractor, not short-clusters — a cluster like `-Atc` is a pure
|
|
190
|
+
// flag, not glue).
|
|
191
|
+
for (const f of extractor.flags) {
|
|
192
|
+
if (f.length === 2 && f.startsWith("-") && !f.startsWith("--")) {
|
|
193
|
+
if (tok.startsWith(f) && tok.length > 2) {
|
|
194
|
+
const sql = tok.slice(2);
|
|
195
|
+
if (sql.length > 0)
|
|
196
|
+
return { sql };
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// 3. bare flag → SQL is the next token.
|
|
202
|
+
if (matchesFlagExactly(tok, extractor)) {
|
|
203
|
+
const v = tokens[i + 1];
|
|
204
|
+
if (typeof v === "string" && v.length > 0)
|
|
205
|
+
return { sql: v };
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Top-level: should this database command be auto-allowed? Consumes the raw
|
|
214
|
+
* tokenized argv (tokens[0] is the binary) and returns true ONLY when the
|
|
215
|
+
* inline SQL was extracted AND classified read-only.
|
|
216
|
+
*/
|
|
217
|
+
export function classifyDbRead(tokens) {
|
|
218
|
+
const binary = tokens[0];
|
|
219
|
+
const spec = DB_CLIENTS.find((c) => c.binaries.includes(binary));
|
|
220
|
+
if (!spec)
|
|
221
|
+
return false;
|
|
222
|
+
const extracted = extractSqlArg(tokens, spec);
|
|
223
|
+
if (!extracted)
|
|
224
|
+
return false;
|
|
225
|
+
return spec.classifySql(extracted.sql);
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=dbReadPolicy.js.map
|