@sema-agent/core
Version:
Stateless, task-oriented AI agent core
453 lines (452 loc) • 18 kB
JSON
{
"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"
}