UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

453 lines (452 loc) • 18 kB
{ "repo": "core", "version": "7.21.0", "from": "6f1349738f7d9d23f81326f1fadcd09fbdca3283", "to": "v7.21.0", "added": [ { "name": "SymlinkChain", "kind": "interface", "tier": "internal", "at": [ "src/engine/harness/types.ts:400", "src/index.ts:511", "src/internal/harness-types.ts:33" ], "criteriaCovered": true, "breaking": false } ], "removed": [], "changed": [], "narrowed": [ { "id": "F2", "source": "behaviorFlips.summary", "text": "NARROWING (allow -> ask), roots face, unlisted programs (#860; main session): a `NAME=value` word offers its VALUE as a path candidate BESIDE the whole word, judged where the value points. ONE arm read by both boundary faces (`collectSegmentBoundaryFindings`, `src/tools/fs/bash-readonly-classifier.ts:3219-3223`) and by the refused-whole word scan (`unlistedSegmentJudgedInside`, `:1099-1112` and `:1140`), including the LEADING environment words bash strips before choosing the program (`:1164`, `:1178`, `:1183`; launcher scan re-headed from the FIRST token at `:4417`). Who needs it: every roots-face deployment — `dd if=/etc/passwd` read outside the roots at ZERO asks under the default doctrine, because the whole word `if=/etc/passwd` contains a separator and resolved to `./if=/etc/passwd`, inside. Who is hurt: roots-face runs of `make PREFIX=/usr/local`, `docker run -e HOME=/root ...` and every other `NAME=<outside path>` spelling now pay one mandated ask (headless: refused). Compensation: path-less and in-roots values stay at zero asks on both faces, and the open face vouches for a plain value (only the deny half speaks there)." }, { "id": "F3", "source": "behaviorFlips.summary", "text": "NARROWING (allow -> ask), BOTH faces, the two spellings the folded text cannot settle (#860, second half): a value carrying a tilde (`assignmentWordPayload`, `src/tools/fs/bash-readonly-classifier.ts:2971-2978`; `value.includes(\"~\")` at `:2976`) and the APPEND form `NAME+=value` (`:2975`, regex `ASSIGNMENT_OR_APPEND_WORD` at `:2969`) are reported UNRESOLVED and ask on both faces, never guessed — bash expands a tilde in such a word only where it is unquoted, and the folded word no longer says which. STATED OVER-FIRE, measured on the merge tree: a LISTED reader handed `NAME=<outside path>` as a POSITIONAL operand (`cat a=/etc/passwd`) now asks on the roots face too (the card carries a `readRootCandidate` way out); the open face vouches for it. Who is hurt: those two spellings on an unlisted program, plus that one listed-reader shape. Compensation: the fail-closed direction, and the program-position reader is deliberately NOT widened to `+=` (a quoted `'A+=x'` is a command NAME to bash and reading it as an assignment would skip the real program — pinned as a negative control)." }, { "id": "F4", "source": "behaviorFlips.summary", "text": "NARROWING (a stored yes no longer answers it) (#850 / C-R52 / DV-817-1; car-850): the write-protection demotion becomes a member of the MANDATE family — `PERSISTED_RULE_MANDATES` (`src/core/ask-origin.ts:298`, now a registered closed set in `docs/CLOSED-SETS.md` with two total tables behind `AssertAllKeysHandled` fences at `:325` and `:358`), with the two new words `write_protection` and `write_protection_unresolved` placed on the ladder at `persistedRuleMandateOf` (`:398-403`). A matching persisted allow rule is DISCLOSED as shadowed (`persistedRuleShadowed` plus the word's own sentence from `MANDATE_DISCLOSURE`, `:345-357`) instead of clearing the ask. Who needs it: upstream parity — the reference CLI runs the write-protection judge BEFORE the general allow rules, so one stored 'allow this from now on' covering the path silently retired every later write onto the protected table. Who is hurt: a deployment whose store answers `allow` for a write tool (core's own DSL mints no such rule — `parseRuleText` refuses an allow on a path tool; an imported foreign settings store can hold one); those writes ask again, once per write. Compensation: an armed classifier and a person still clear it, and the durable park stays budgetable (`safetyAxisOf` is unchanged — this is not an irreversibility mandate)." }, { "id": "F5", "source": "behaviorFlips.summary", "text": "NARROWING (the same escape through a different door) (#850): the judge now ALSO runs over an ask another layer already raised (`src/core/gate-fold.ts:360-361`, `writeProtectionStandingAsk`) and merges the mandate ALONE — `decision !== undefined && !writeProtectionStandingAsk` at `:381` is what keeps the standing ask's action, message, provenance and disclosures BYTE-IDENTICAL. Who needs it: a deployment whose write face asks FIRST (`FsWriteGatePolicyOptions.defaultWrite: \"ask\"`, or any policy that asks) never reached the judge at all, so the standing rule cleared that question with the table never consulted. Who is hurt: those deployments' protected writes now ask again per write. Compensation: the pin is byte-equality against the same run with no judge mounted (G2.d), so nothing but the mandate word is merged." }, { "id": "F6", "source": "behaviorFlips.summary", "text": "ADDITIVE word + NARROWING on the automatic lanes, and one LOUD failure (#850): the judge's OTHER non-clear verdict carries `write_protection_unresolved` (`src/core/ask-origin.ts:398-401`; the pass cells `GatePass.writeProtected` / `GatePass.writeProtectionUnresolved` at `src/core/runner/gate-exit.ts:355-361`, written at `gate-fold.ts:378-379` through the two verdict predicates `writeProtectionMandated` / `writeProtectionUnresolved`, `src/core/write-protect.ts:730/:736`, contract sentence `write_protection.verdict_is_the_mandate`). The ACTION stays the owning arm's (a surviving allow is still denied; an ask another layer raised is untouched). TWO COLUMNS, not one word: `MANDATE_LANE_ANSWERS` (`ask-origin.ts:316-323`) gives this word `classifier: false` (the table's answer for this call is UNKNOWN) and `sandboxAdmission: true` (containment holds the effect whether or not the target is on the table). A judge that TIMES OUT or THROWS produces the same verdict AND reaches `RunnerDeps.onError(phase:\"hook\")` naming the judge, the tool and the callId (`gate-fold.ts:366-375` -> `traceHookCrash`, `gate-exit.ts:197` -> `notifyHookError`, `prepare-ask-lane.ts:342-344`). Who needs it: the cost of F4 is paid in AUTONOMY, so a broken judge must not silently stop every covered write from being handled automatically. Who is hurt: a deployment whose judge is SLOW keeps its writes behind a person or an approver seat on the ask-first path too." }, { "id": "F7", "source": "behaviorFlips.summary", "text": "NARROWING (allow -> ask), one alias shape, every deployment whose environment declares the new seat (#851 / C-R53 / DV-817-3; car-851): the write-protection judge judges every hop of the target's LEAF symlink chain (`src/core/write-protect.ts:673-680`), consulted LAST and ONLY for a terminal that names no row and is not inside the data root; a hop is judged as a SPELLING (`judgeSpelled` at `:614` — the data-root passthrough and the three readings apply) and a hit reports `via: \"chain\"` (`:679`) with its own ask sentence (`writeProtectionTightenDecision`, `:705-707`). Who needs it: `plainname -> <home>/.ssh/newfile` where `<home>/.ssh` is itself a symlink to an unguarded directory was cleared by both judges (measured on 7.20.0 by the black-box acceptance: zero asks, `disposition.kind: \"allowed\"`), while the reference CLI (2.1.259) judges every variant a path has and asks there. Who is hurt: such writes ask once where they execute (headless: refused). Compensation: the exemption by IDENTITY outranks a hop and ends the judgment; a 7.20.0 spelling or target hit keeps its own word and its byte-identical sentence; an environment that declares no chain is byte-identical." }, { "id": "F8", "source": "behaviorFlips.summary", "text": "NARROWING (allow -> deny), the same shape on the sensitive-path deny set (#851): `createSensitivePathPolicy().check` judges each hop as a WRITTEN spelling (both readings, no data-root passthrough — that exemption compares canonical keys and ends the judgment by identity) and refuses naming the pattern (`src/core/sensitive-path-policy.ts:470-484`; reached only when `relative === null`, i.e. the terminal is not inside the data root). Who needs it: deployments that mount the recommended deny set (the server does). Who is hurt: an aliased write whose chain crosses a guarded row is refused rather than executed, on every lane including headless. Compensation: pinned as CURRENT reach and not as a promise — the deny set still does not judge the SPELLING itself, so the direct `~/.ssh/x` under a symlinked `~/.ssh` stays allowed by this set (the table asks) while `plainname -> ~/.ssh/x` is refused; a follow-up ticket carries the spelling arm." }, { "id": "F14", "source": "behaviorFlips.summary", "text": "SECURITY / NARROWING (allow -> refuse), win-family trees only (#840 / C-R54; car-840): the UNC lexical arm of `canonicalizeTarget` is deleted (`foldUncLexically` with it), so a win-form `\\\\host\\share\\...` spelling takes the ordinary resolution path — resolve, follow the symlink chain, canonicalize the deepest existing ancestor (`src/tools/fs/safety.ts:1132` onwards; the probing half `probeRealTarget` at `:1240`). Who needs it: on a UNC-rooted workspace a link INSIDE the share was never followed, so a target spelled in-root whose real destination is another share (or a guarded directory) was admitted by root containment, the sensitive-path deny set, the fs write gate, the session `allowDirs` and a skill manifest's `allowPaths` alike — measured on the previous tree, all four fences. Who is hurt: such a spelling is now refused by whichever fence is wired (headless: the write/read is refused rather than executed); a UNC-rooted workspace whose files are ordinary files is unchanged. Compensation: non-win trees are byte-identical spelling for spelling, and the negative-control matrix was re-run against the previous tree." } ], "closedSetMembers": [ { "set": "GateWaitFailure", "at": [ "src/core/gate-bounded-await.ts:24" ], "newSet": true, "added": [ "aborted", "timeout" ], "removed": [], "breaking": false, "criteriaCovered": false }, { "set": "PERSISTED_RULE_MANDATES", "at": [ "src/core/ask-origin.ts:298" ], "newSet": true, "added": [ "operator_always", "probe_mandate", "probe_unanswered", "tool_marks", "write_protection", "write_protection_unresolved" ], "removed": [], "breaking": false, "criteriaCovered": false } ], "closedSetsUnreadable": [], "typeKeys": [ { "type": "ExecutionEnv", "at": [ "src/engine/harness/types.ts:599" ], "added": [ "canonicalChain" ], "removed": [], "breaking": false, "criteriaCovered": true }, { "type": "RemoteExecutionEnv", "at": [ "src/core/remote-env.ts:313" ], "added": [ "canonicalChain" ], "removed": [], "breaking": false, "criteriaCovered": false }, { "type": "RunMemoryConsolidationOptions", "at": [ "src/core/memory-engine/consolidation-driver.ts:234" ], "added": [ "onNotice" ], "removed": [], "breaking": false, "criteriaCovered": false }, { "type": "RunnerDeps", "at": [ "src/core/runner-deps.ts:104" ], "added": [ "autoRunOnRecommendation" ], "removed": [], "breaking": false, "criteriaCovered": true }, { "type": "WiringFacts", "at": [ "src/core/wiring-manifest.ts:378" ], "added": [ "autoConsolidationOnRecommendation" ], "removed": [], "breaking": false, "criteriaCovered": false }, { "type": "WiringManifest", "at": [ "src/core/wiring-manifest.ts:164" ], "added": [ "autoConsolidation" ], "removed": [], "breaking": false, "criteriaCovered": true } ], "declarationMoves": [], "mintMoves": [], "criteriaObserves": [ { "kind": "engineNoticeCode", "name": "config.execution_env_capability_invalid", "assertions": [ "G4.a" ] }, { "kind": "engineNoticeCode", "name": "memory.consolidation_recommended", "assertions": [ "G5.c" ] }, { "kind": "taskEventField", "name": "tool_end.errorCode", "assertions": [ "G7.b" ] }, { "kind": "taskEventField", "name": "tool_end.isError", "assertions": [ "G7.a" ] }, { "kind": "taskEventField", "name": "tool_end.structured", "assertions": [ "G7.e" ] }, { "kind": "typeMember", "name": "AskRequest.toolName", "assertions": [ "G2.d" ] }, { "kind": "typeMember", "name": "BashReadonlyRootBoundary.denyMatch", "assertions": [ "G1.b" ] }, { "kind": "typeMember", "name": "BashReadonlyRootBoundary.face", "assertions": [ "G1.a", "G1.c", "G1.e" ] }, { "kind": "typeMember", "name": "CompoundReadonlyVerdict.outOfRootPaths", "assertions": [ "G1.d" ] }, { "kind": "typeMember", "name": "ExecutionEnv.canonicalChain", "assertions": [ "G3.b", "G3.f" ] }, { "kind": "typeMember", "name": "ExecutionEnv.canonicalPath", "assertions": [ "G7.d" ] }, { "kind": "typeMember", "name": "FsWriteGatePolicyOptions.defaultWrite", "assertions": [ "G2.c" ] }, { "kind": "typeMember", "name": "FsWriteGatePolicyOptions.exemptDirs", "assertions": [ "G7.f" ] }, { "kind": "typeMember", "name": "GateDisposition.classifier", "assertions": [ "G8.a" ] }, { "kind": "typeMember", "name": "GateDisposition.deniedBy", "assertions": [ "G2.e", "G3.c" ] }, { "kind": "typeMember", "name": "GateDisposition.kind", "assertions": [ "G8.d" ] }, { "kind": "typeMember", "name": "GateOutcome.settlement", "assertions": [ "G8.b" ] }, { "kind": "typeMember", "name": "LlmConsolidationPlan.minting", "assertions": [ "G6.a" ] }, { "kind": "typeMember", "name": "LlmConsolidationPlan.minting.writingChannel", "assertions": [ "G6.b" ] }, { "kind": "typeMember", "name": "PermissionResult.persistedRuleShadowed", "assertions": [ "G2.a" ] }, { "kind": "typeMember", "name": "ReadDenyMatcher.matchTarget", "assertions": [ "G3.e" ] }, { "kind": "typeMember", "name": "RemoteExecutionEnv.capabilities", "assertions": [ "G4.b" ] }, { "kind": "typeMember", "name": "ReversibilityVerdict.mandated", "assertions": [ "G1.f" ] }, { "kind": "typeMember", "name": "ReversibilityVerdict.readRootCandidate", "assertions": [ "G7.c" ] }, { "kind": "typeMember", "name": "RunnerDeps.autoRunOnRecommendation", "assertions": [ "G5.b" ] }, { "kind": "typeMember", "name": "RunnerDeps.memoryConsolidation", "assertions": [ "G5.d" ] }, { "kind": "typeMember", "name": "RunnerDeps.memoryConsolidationDriver", "assertions": [ "G5.e" ] }, { "kind": "typeMember", "name": "RunnerDeps.onError", "assertions": [ "G2.f" ] }, { "kind": "typeMember", "name": "RunnerDeps.writeProtectedPaths", "assertions": [ "G2.g" ] }, { "kind": "typeMember", "name": "Settlement.kind", "assertions": [ "G2.b" ] }, { "kind": "typeMember", "name": "Settlement.who.approver", "assertions": [ "G8.c" ] }, { "kind": "typeMember", "name": "SymlinkChain.hops", "assertions": [ "G3.d" ] }, { "kind": "typeMember", "name": "TaskResult.stats.humanReview", "assertions": [ "G8.e" ] }, { "kind": "typeMember", "name": "WiringManifest.autoConsolidation", "assertions": [ "G5.a" ] }, { "kind": "typeMember", "name": "WriteProtectionVerdict.via", "assertions": [ "G3.a" ] } ], "consumersHint": { "cli": [ "ExecutionEnv", "RemoteExecutionEnv", "RunnerDeps", "WiringFacts", "WiringManifest" ], "server": [ "ExecutionEnv", "RemoteExecutionEnv", "RunMemoryConsolidationOptions", "RunnerDeps", "WiringFacts", "WiringManifest" ], "sdk": [ "ExecutionEnv", "RunnerDeps", "WiringManifest" ], "note": "scanned cli@0f132216 server@6e121caf sdk@6209e5e — a name listed is MENTIONED there today, which is evidence of consumption, not proof of it" }, "breakingCount": 0, "breakingItems": [], "toResolvedAt": "abb48087842215283113c231f31d255691a3b8dc" }