@hasna/skills 0.10.71 → 0.10.73

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/README.md CHANGED
@@ -113,6 +113,27 @@ skills hook install --selection-profile default --json
113
113
  skills hook install --selection-profile default --apply --json
114
114
  ```
115
115
 
116
+ `--include-vendor` resolves configured plugin discovery, so it needs a reviewed
117
+ `--discovery-inputs` file when a configured plugin declares hooks. Without it,
118
+ `skills migrate native --include-unmanaged` archives user and project copies
119
+ only. Migration never archives Codex system skills (`.codex/skills/.system`),
120
+ even with `--include-vendor`. Codex refreshes its OpenAI marketplace plugin
121
+ caches on its own; retire those copies with a Codex `[[skills.config]]` rule
122
+ (`name = "<plugin>:<skill>"` or the exact `path` of the `SKILL.md`, with
123
+ `enabled = false`) rather than by archiving. The live Codex prompt hook accepts
124
+ a name rule only for a copy with no Hasna marker, its remote install receipt, a
125
+ refresh-safe plugin and no rule that enables it. Other checks (`skills hook
126
+ install` reusing a saved review, `skills hook trust`) do not accept a
127
+ hand-added name rule; for them, and for system skills, run `skills hook install
128
+ --agent codex --discovery-inputs <file> --apply` with a fresh discovery review
129
+ (no `--discovery-inputs` on an automatic binding; add a fresh
130
+ `--codex-native-catalog <file>` for a plugin Skills has enrolled), which writes
131
+ and records the disables. Run the same refresh after editing `config.toml` when
132
+ the binding's recorded configuration witness counts the edit: on automatic
133
+ bindings, after removing a rule that enables skills, and on reviewed bindings
134
+ whose witness counts disable rows. A `NATIVE_SKILL_DRIFT` refusal names the
135
+ step for each copy it lists, including that follow-up.
136
+
116
137
  Without `--agent`, hook installation selects configured or detected consumers:
117
138
  existing managed bridge bindings, provider-specific home configuration, or an
118
139
  installed native CLI found through PATH. Detection reads metadata and never
@@ -207,6 +228,82 @@ is recorded at `agent-hooks/codex-native-policy-acceptance.json`; user,
207
228
  repository and project copies still refuse, and format or digest checks never
208
229
  prove the runtime policy on their own.
209
230
 
231
+ A Codex plugin refresh can deposit a new vendor skill in `~/.codex/plugins/cache`
232
+ (for example `openai-curated-remote/sites/1.0.2-remix-b.1/skills/sites-remix`).
233
+ Skill payloads still reach agents only through the Skills CLI, so an enabled
234
+ vendor copy keeps blocking the Codex prompt hook. Retire it by disabling it in
235
+ `~/.codex/config.toml`. The plugin-qualified name survives later version
236
+ directories, so prefer it:
237
+
238
+ ```toml
239
+ [[skills.config]]
240
+ name = "sites:sites-remix"
241
+ enabled = false
242
+ ```
243
+
244
+ An exact `path = ".../sites/1.0.2-remix-b.1/skills/sites-remix/SKILL.md"` rule
245
+ also works, but only for that version. On the live Codex prompt hook only, a
246
+ copy is then exempt when every condition holds:
247
+
248
+ - it is package-classified plugin content in `openai-bundled`, `openai-curated`
249
+ or `openai-curated-remote` (exact names), with Codex's remote install receipt
250
+ for the remote marketplace;
251
+ - it carries no Skills or Hasna marker;
252
+ - every cached version of its plugin passes the vendor refresh check described
253
+ below for membership changes, so the plugin can load nothing that the
254
+ inventory and this rule do not account for, and no version is a link;
255
+ - a `[[skills.config]]` rule names its exact path or its plugin-qualified name
256
+ with `enabled = false`, and no matching rule enables it.
257
+
258
+ A whole-plugin `[plugins."sites@openai-curated-remote"] enabled = false` table
259
+ is not enough on its own: remote state can enable a locally disabled remote
260
+ plugin, and a cache copy carries no proof that its plugin source is
261
+ configuration-controlled, so the copy keeps blocking until a path or name rule
262
+ retires it. This exemption only adds to the guard's existing ones; every copy
263
+ that the guard already accepts, such as an exact-path disable in any plugin-cache
264
+ marketplace or a reviewed plugin control, stays accepted on the live hook. Once
265
+ the hook tolerates one of the witness changes below, a state the strict guard
266
+ refuses outright, plugin-cache copies are exempt only through this vendor rule.
267
+
268
+ Instead of editing by hand, `skills hook native-catalog` followed by
269
+ `skills hook install --agent codex --codex-native-catalog <file> --apply` writes
270
+ the same rules through the guarded install path.
271
+
272
+ The hook also tolerates, with a visible `systemMessage` warning, only the
273
+ discovery-witness changes such a refresh provably makes:
274
+ - appended vendor disable rules or vendor plugin disables in `config.toml`,
275
+ accepted when removing exactly those entries or restoring a formerly enabled
276
+ vendor flag reproduces the reviewed digest. Candidates are tried smallest edit
277
+ first, at most 128; with a typed settings witness that covers any change to up
278
+ to two of at most eight vendor plugin tables. A change beyond the bound refuses;
279
+ - directory membership changes under an allowlisted vendor marketplace that
280
+ passes the vendor refresh check: every plugin version has exactly one
281
+ manifest, `.codex-plugin/plugin.json`, alone in its directory, whose keys are
282
+ only `name`, `version`, `description`, `author`, `homepage`, `repository`,
283
+ `license`, `keywords`, `interface`, `skills` (the default `./skills/` only) and
284
+ `apps`. So hooks in any form, commands, agents, extensions, MCP servers and
285
+ unknown keys refuse. There is no `hooks`, `commands` or `agents` directory, no
286
+ `hooks.json` or `.mcp.json` (names compared without regard to case), no root,
287
+ `.claude-plugin` or `.cursor-plugin` manifest, no link
288
+ and no `SKILL.md` below a dot-named or `node_modules` directory, or nested
289
+ in another skill. One exception keeps
290
+ today's vendor plugins working: every current OpenAI vendor plugin declares
291
+ its connector as `apps = "./.app.json"`, so exactly that declaration is
292
+ allowed when `.app.json` is a regular file of at most 1 MiB, strict UTF-8, that
293
+ passes the same connector validation reviewed plugin controls use (one `apps`
294
+ object; app entries with only `id`, `category` and `required`, of the right
295
+ types; no duplicate keys). Any other connector path, a file that fails that
296
+ validation, or the file without the declaration refuses.
297
+
298
+ Every other copy and change still refuses with `NATIVE_SKILL_DRIFT`, including:
299
+ - user, project, `.system` and Hasna-marketplace copies that the guard does not
300
+ already exempt;
301
+ - an enabled vendor copy whose plugin is only disabled by a whole-plugin table;
302
+ - capability-control changes and whole-cache membership changes;
303
+ - plugin activation, including a vendor plugin gaining `enabled = true`;
304
+ - missing coverage and changed roots;
305
+ - every install, trust, migrate and sync check.
306
+
210
307
  Native hook invocations must use their installed adapter's selection profile.
211
308
  An old client command or environment override naming another profile refuses
212
309
  before synchronization or context loading. Review the hook installation and
@@ -465,6 +562,20 @@ is bounded to 20,000 discovery entries and 4 MiB of UTF-8 path metadata before
465
562
  directory entries are retained or sorted. Unsupported special files and unsafe
466
563
  directory links are refused.
467
564
 
565
+ Codex 0.162.1 loads `SKILL.md` files below `node_modules` inside its skill
566
+ roots, so Codex inventory and the prompt guard walk `node_modules` in
567
+ `~/.codex/skills`, `~/.agents/skills`, each project's `.codex/skills` and
568
+ `.agents/skills` and configured plugin skill roots. In `~/.codex/plugins/cache`
569
+ they walk it only where it can lie in a plugin skill root: below a version's
570
+ default `skills` directory or a manifest-declared skills path, or as a
571
+ marketplace, plugin or version directory named `node_modules`; an unreadable
572
+ manifest walks every `node_modules` in its version. Dot-named directories such
573
+ as `node_modules/.pnpm`, a plugin version's own dependencies and any
574
+ `node_modules` outside a skill root stay unscanned, because Codex does not load
575
+ them; other agents' roots keep skipping `node_modules`. Depth limits are
576
+ unchanged: Codex reads six directory levels below a root, and an ordinary root
577
+ records a skill four levels down and refuses anything deeper.
578
+
468
579
  Native archives persist a version 2 recovery journal before moving payloads;
469
580
  `migrate native --json --apply` returns its `receiptPath`. The journal records
470
581
  every source, archive path, expected hash, and move status, then marks successful
@@ -550,6 +661,10 @@ non-null `sha256`, Skills also requires plain byte witnesses for that plugin's
550
661
  `hooks/hooks.json` (when present) and any string `hooks` target in the
551
662
  manifest. A manifest listed as `claude-plugin-manifest-v1` (the form Skills
552
663
  writes into the stored policy) or as `path-bytes` triggers no such requirement.
664
+ The version-root `.orphaned_at` timestamp that Claude rewrites at startup is
665
+ bound by presence only, as `claude-plugin-orphaned-at-v1`, and is still
666
+ strictly validated; see
667
+ [the full scope](docs/plugin-admission.md#reviewed-plugin-hook-attestation-scope).
553
668
  For plugins outside receipt-backed admission (a `claude-plugin-registry`
554
669
  witness, see the
555
670
  [discovery transition contract](docs/plugin-admission.md#discovery-transition-contract)),
@@ -622,6 +737,25 @@ receipt permits only verified registry/cache transitions. See
622
737
  [plugin admission](docs/plugin-admission.md) for the package, review and runtime
623
738
  contracts; these commands do not change live agent registrations or settings.
624
739
 
740
+ The read-only `skills hook review-claude-candidate` check requires its reviewed
741
+ discovery inputs to bind the user settings, both project settings files and
742
+ both plugin registries. A project `.claude/settings.json` or
743
+ `.claude/settings.local.json` that the review omits is exempt only when no other
744
+ expected source shares its path and it is either absent, or present with exact
745
+ preimage bytes that parse strictly as one JSON object (strict UTF-8, no
746
+ byte-order mark, comments, trailing content or duplicate keys) and carry only
747
+ allowlisted settings: `permissions` with `allow`, `deny`, `ask` and
748
+ `defaultMode`, plus `outputStyle`, `model`, `theme`, `effortLevel`, `verbose`
749
+ and `spinnerTipsEnabled`. Any discovery key (`enabledPlugins`,
750
+ `extraKnownMarketplaces`, `additionalMarketplaces`, `skillOverrides`), `env`,
751
+ `hooks`, `permissions.additionalDirectories` or unknown key keeps the file
752
+ required. The allowlist is a reviewed list of settings, not a proof that Claude
753
+ cannot discover anything from the file. This admits the permissions-only
754
+ `~/.claude/settings.local.json` that Claude writes when it saves a permission
755
+ rule. Each exempt file is re-checked against its exact preimage until the
756
+ result; user settings and the registries are never exempt. See
757
+ [prospective Claude candidate review](docs/plugin-admission.md#prospective-claude-candidate-review).
758
+
625
759
  If your home `.claude` or `.codex` directory intentionally links to another
626
760
  directory within your home, add `--allow-root-aliases` to hook installation and
627
761
  native migration. The plan records and rechecks the exact link and target;
@@ -819,16 +953,37 @@ a skill. Cached use is explicit and expires after 24 hours. When a session
819
953
  receipt expires, the hook authenticates the current profile. If every loaded
820
954
  selection still has identical metadata and bundle digest, it archives the old
821
955
  receipt and advances that session to the current profile under an exact receipt
822
- precondition. Unloaded selections may be added or removed. If a loaded selection
823
- changed or disappeared, no instructions are delivered until the explicit
824
- reconciliation below. A missing cache may be recovered through authenticated context.
956
+ precondition. Unloaded selections may be added or removed. If renewal is refused
957
+ because the same profile moved on (a loaded selection changed or disappeared, or
958
+ another pinned selection's policy, membership or lifecycle changed), the managed
959
+ hook applies the explicit reconciliation planner and applier below to the
960
+ authenticated current revision: it archives the old receipt, retains unchanged
961
+ loaded bundles, retires changed or removed ones and delivers context for the
962
+ prompt. The first hook context that succeeds and reaches the agent afterwards
963
+ names each retired loaded skill once and how to reload its replacement; earlier
964
+ instructions from those skills must not be relied on. Identity, integrity and receipt failures, an
965
+ unavailable authority and anything the planner refuses keep their refusal;
966
+ session write-lock contention keeps `SESSION_WRITE_BUSY`. A
967
+ session created from a project lock (`.skills/selection.lock.json`) records
968
+ that lock in its receipt and is never migrated automatically, whatever
969
+ directory it later prompts from. A session without that record is not migrated
970
+ while a lock sits in the prompt's working directory or any ancestor. A loaded
971
+ skill whose lifecycle epoch rotated while its bytes stayed the same also keeps
972
+ the refusal. If the migration cannot finish within the four-second renewal window,
973
+ the hook reports `SESSION_RECONCILIATION_REQUIRED` with that reason and the next
974
+ prompt retries. Turn this off station-wide with `skills config set
975
+ sessionPinMigration off --global` (the key is refused without `--global`, and a
976
+ global configuration that is a symlink, cannot be read or cannot be parsed also
977
+ turns it off); the hook
978
+ then keeps today's refusal until the explicit reconciliation below. A missing
979
+ cache may be recovered through authenticated context.
825
980
 
826
981
  After verifying the native bridge and input, ordinary lifecycle hooks continue
827
982
  without skill payload on typed hosted authentication/refusal/availability failures,
828
983
  context timeouts, valid concurrent writers, context CAS conflicts, or required
829
984
  session reconciliation. They emit a fixed warning that Skills-dependent actions
830
985
  are unavailable; previously loaded instructions are not an authorization fallback.
831
- The hook does not rewrite a session pin or remove another writer’s lock to bypass these refusals. Explicit `skills
986
+ Apart from the automatic migration above, the hook does not rewrite a session pin or remove another writer’s lock to bypass these refusals. Explicit `skills
832
987
  load`, `skills context`, and `skills run` still refuse unavailable or unauthorized
833
988
  instructions. Native skill tool guards still deny unmanaged payloads. Invalid
834
989
  input/receipts, integrity or authority mismatches, unknown failures, and native
@@ -908,6 +1063,209 @@ reports an incomplete outcome, inspect `sessions show` and the preserved
908
1063
  operation receipt before retrying. The archive contains both original and
909
1064
  prepared bytes even if the final receipt write fails.
910
1065
 
1066
+ ### Claude installer witness CLI (`capture-claude-installer`)
1067
+
1068
+ ```text
1069
+ skills hook capture-claude-installer --request /absolute/request.json --json
1070
+ ```
1071
+
1072
+ Installers obtain Skills-owned witnesses through the configured Skills command
1073
+ instead of reimplementing them. Every request is a JSON object of at most
1074
+ 64 KiB with exactly the keys of one operation: `schema`
1075
+ (`skills.claude-installer-capture/v1`), `commandPath` (the normalized absolute
1076
+ path of the configured Skills command), `operation`, and that operation's
1077
+ inputs. The command checks that `commandPath` resolves to this installed
1078
+ `@hasna/skills` CLI and runtime, before and after the operation, and prints
1079
+ one receipt on stdout with exit status 0:
1080
+
1081
+ ```json
1082
+ {
1083
+ "schema": "skills.claude-installer-capture-result/v1",
1084
+ "producer": {
1085
+ "schema": "skills.cli-producer-identity/v1",
1086
+ "configuredCommandPath": "<commandPath>",
1087
+ "physicalLauncher": { "path": "<absolute>", "sha256": "<64 hex>" },
1088
+ "runtimeEntry": { "path": "<absolute>", "sha256": "<64 hex>" },
1089
+ "runtime": { "path": "<absolute>", "sha256": "<64 hex>" },
1090
+ "package": { "name": "@hasna/skills", "version": "<x.y.z>", "manifestSha256": "<64 hex>" }
1091
+ },
1092
+ "operation": "<operation>",
1093
+ "result": {}
1094
+ }
1095
+ ```
1096
+
1097
+ A refusal prints nothing on stdout, exits 1 and writes one line to stderr,
1098
+ `CODE: fixed text`, which never echoes request or file contents. The
1099
+ per-file operations (`settings`, `marketplace-registry`,
1100
+ `managed-plugin-registry`, `path-bytes`, `discovery-directories`,
1101
+ `plugin-manifest` and `candidate`) are described in
1102
+ [docs/plugin-admission.md](docs/plugin-admission.md#claude-installer-witness-cli).
1103
+
1104
+ #### Verify the stored Claude discovery binding (`discovery-binding`)
1105
+
1106
+ `discovery-binding` checks the Claude discovery binding stored in the Skills
1107
+ policy (`bridge.discovery.claude` in `agent-policy.json`) with the discovery
1108
+ verification the managed prompt hook runs on it: the root alias recheck,
1109
+ `verifyAgentDiscovery` and, for an automatic binding, the automatic closure
1110
+ check. It then reports every stored witness. An installer calls it once
1111
+ instead of re-capturing each stored witness. Per-file capture requires every
1112
+ file to exist, so it refuses witnesses that Skills keeps on purpose after
1113
+ Claude deletes a retired plugin-cache version; this operation reports those as
1114
+ `retained-absent`. It does not check the bridge files, hook commands or
1115
+ project-directory settings, which the hook checks separately.
1116
+
1117
+ The request has exactly these keys:
1118
+
1119
+ ```json
1120
+ {
1121
+ "schema": "skills.claude-installer-capture/v1",
1122
+ "commandPath": "/home/me/.local/bin/skills",
1123
+ "operation": "discovery-binding",
1124
+ "policyPath": "/home/me/.hasna/skills/agent-policy.json"
1125
+ }
1126
+ ```
1127
+
1128
+ `policyPath` must equal `agent-policy.json` in the data directory the installed
1129
+ package resolves (`$HASNA_SKILLS_DIR`, default `~/.hasna/skills`), as for the
1130
+ `candidate` operation.
1131
+
1132
+ The receipt's `result`:
1133
+
1134
+ ```json
1135
+ {
1136
+ "schema": "skills.claude-discovery-binding-verification/v1",
1137
+ "ok": true,
1138
+ "policySha256": "<64 hex>",
1139
+ "discoverySha256": "<64 hex>",
1140
+ "agent": "claude",
1141
+ "method": "reviewed",
1142
+ "rootCount": 2,
1143
+ "sources": [
1144
+ { "index": 0, "path": "/home/me/.claude/settings.json", "hashMode": "claude-settings-v4", "format": null, "sha256": "<64 hex>", "status": "current" },
1145
+ { "index": 3, "path": "/home/me/.claude/plugins/cache/market/plugin/1.0.0/.claude-plugin/plugin.json", "hashMode": "claude-plugin-manifest-v1", "format": null, "sha256": "<64 hex>",
1146
+ "status": "retained-absent", "retention": { "rule": "absent-retired-claude-version", "root": "/home/me/.claude/plugins/cache/market/plugin/1.0.0", "witness": "claude-plugin-manifest-v1" } }
1147
+ ],
1148
+ "directories": [ { "index": 0, "path": "/home/me/.claude/plugins/cache/market/plugin/2.0.0", "status": "current" } ],
1149
+ "failures": []
1150
+ }
1151
+ ```
1152
+
1153
+ - `policySha256` is the SHA-256 of the exact policy file bytes that were
1154
+ verified. It is the value a prospective review request passes as
1155
+ `skills.policySha256`. The policy is read again before the result is
1156
+ returned; if it changed, the request is refused.
1157
+ - `discoverySha256` is `hashClaudeDiscoveryBinding` of the stored binding, the
1158
+ same digest the `candidate` operation returns.
1159
+ - `agent`, `method` (`reviewed` or `automatic`) and `rootCount` (the number of
1160
+ stored discovery roots) come from the stored binding.
1161
+ - `sources` has one row per stored source, in stored order. `index` is the
1162
+ position in `bridge.discovery.claude.sources`. `path`, `hashMode`, `format`
1163
+ and `sha256` repeat the stored values: `hashMode` is `null` for a legacy
1164
+ full-file witness without a mode, `format` is `null` unless the source is a
1165
+ field projection (Claude reviews also keep the automatic `json` projection of
1166
+ `settings.json`), and a `null` `sha256` pins absence. `status` is one of:
1167
+ - `current`: the source's own witness reproduces the stored digest now.
1168
+ - `retained-absent`: the file was not read, because the
1169
+ `absent-retired-claude-version` rule keeps its stored digest. The rule
1170
+ holds when the whole version directory `retention.root`
1171
+ (`<...>/.claude/plugins/cache/<marketplace>/<plugin>/<version>`) is absent,
1172
+ the reviewed witnesses of `settings.json` and
1173
+ `plugins/installed_plugins.json` are current, settings enable the plugin
1174
+ and no settings hook names that directory, every registration of the
1175
+ plugin is a valid user or project row for one existing sibling version in
1176
+ the same plugin directory (with a user row among them), no registration of
1177
+ any plugin points into or contains the absent directory, and the sibling's
1178
+ `.claude-plugin/plugin.json` is bound and current. It applies only to
1179
+ reviewed bindings. `retention.witness` names the retained witness class:
1180
+ `bytes` (a full byte witness, `hashMode` `null` or `bytes`),
1181
+ `claude-plugin-manifest-v1` or `claude-plugin-orphaned-at-v1` (the typed
1182
+ presence witness of a version-root `.orphaned_at` marker). No other
1183
+ witness is retained: `path-bytes`, field projections and typed settings
1184
+ or registry witnesses stay checked. Settings and registry files are
1185
+ never retained.
1186
+ - `drift`, with `reason`: `MISSING` (the path is absent and the rule does
1187
+ not retain it), `APPEARED` (the stored witness pins absence and the path
1188
+ now exists), `CHANGED` (the path exists but its witness does not
1189
+ reproduce the stored digest or refuses the current file, for example
1190
+ edited settings or a symlink), `MARKER_INVALID` (a
1191
+ `claude-plugin-orphaned-at-v1` marker exists but is not a valid Claude
1192
+ marker; see below) or `REFUSED` (the path's presence could not be
1193
+ determined).
1194
+ - `directories` has one row per stored directory membership witness, with
1195
+ `status` `current`, or `drift` with `reason` `MEMBERSHIP_CHANGED` or
1196
+ `REFUSED`.
1197
+ - `failures` lists every drifted source and directory as
1198
+ `{ "kind": "source" | "directory", "index", "path", "reason" }`, followed by
1199
+ `{ "kind": "binding", "reason": "DISCOVERY_REFUSED" }` whenever the hook's
1200
+ own discovery verification refuses the binding. That covers checks no single
1201
+ row shows, such as a changed root alias, a retired directory that reappeared
1202
+ during verification or an invalid witness combination.
1203
+ - `ok` is `true` only when `failures` is empty: the hook's verification
1204
+ accepts the binding, every source is `current` or `retained-absent` and
1205
+ every directory is `current`.
1206
+
1207
+ `ok: false` is a completed verification and still exits 0, so the caller
1208
+ reads which witnesses drifted and why. Installers must require `ok === true`.
1209
+ To bind the result, compare each `sources` row (`index`, `path`, `hashMode`,
1210
+ `format`, `sha256`) with the binding they read themselves, compare
1211
+ `discoverySha256` with the `candidate` capture and `policySha256` with the
1212
+ policy bytes they review, and repeat the call immediately before the first
1213
+ native change, because the verdict describes one moment.
1214
+
1215
+ Claude writes `.orphaned_at` at the root of a plugin-cache version it plans to
1216
+ delete (`<...>/.claude/plugins/cache/<marketplace>/<plugin>/<version>/.orphaned_at`),
1217
+ rewrites its timestamp at each start, may replace the file, and later deletes
1218
+ the marker or the whole version. A fresh Skills discovery review binds such a
1219
+ marker as `claude-plugin-orphaned-at-v1`, a witness of its presence only, and
1220
+ `discovery-binding` reports it with that witness's own check, exactly as the
1221
+ hook does:
1222
+
1223
+ - A rewritten timestamp or a replaced file (new inode) is `current`, provided
1224
+ the marker is a regular, non-symlink file owned by the current user whose
1225
+ content is exactly 13 ASCII digits without a leading zero (no byte-order
1226
+ mark, no newline).
1227
+ - Any other content, a symlink or another owner is `drift` with
1228
+ `MARKER_INVALID`.
1229
+ - A deleted marker whose version directory still exists is `drift` with
1230
+ `MISSING`; the hook refuses this state too.
1231
+ - When Claude deletes the whole version directory and the registered sibling
1232
+ version stays bound and current, the marker and every other retainable
1233
+ witness below that directory are `retained-absent` under
1234
+ `absent-retired-claude-version`.
1235
+ - A recreated version directory is no longer absent, so each of its witnesses
1236
+ is checked again: a valid marker and an unchanged manifest are `current`,
1237
+ and a missing marker is `drift` with `MISSING`.
1238
+ - Marker witnesses stored by an older review as `bytes` or `path-bytes` keep
1239
+ their exact meaning and report `CHANGED` once Claude rewrites the marker,
1240
+ until a fresh discovery review rebinds them as typed witnesses.
1241
+
1242
+ Installers must not restore, copy back or rewrite `.orphaned_at` files, or any
1243
+ other witness file, to make a binding verify. A restored file has a new inode,
1244
+ which a `path-bytes` witness rejects, and it can hide a change Claude made. Act
1245
+ on the `discovery-binding` result instead. When it reports drift, the binding
1246
+ needs a fresh Skills discovery review (`skills hook install` with fresh
1247
+ `--discovery-inputs`), not a file repair.
1248
+
1249
+ Refusal codes for this operation, besides the shared request and producer
1250
+ codes (`INVALID_REQUEST_FILE`, `INVALID_REQUEST_JSON`, `INVALID_REQUEST`,
1251
+ `INVALID_SCHEMA`, `INVALID_REQUEST_FIELDS`, `INVALID_PATH`,
1252
+ `PRODUCER_UNVERIFIED`, `PRODUCER_CHANGED` and `CAPTURE_REJECTED`):
1253
+
1254
+ - `POLICY_PATH_MISMATCH`: `policyPath` is not the package-owned policy path.
1255
+ - `POLICY_UNAVAILABLE`: the policy file is absent.
1256
+ - `POLICY_UNREADABLE`: the policy is not a regular, bounded, valid managed
1257
+ policy file.
1258
+ - `BINDING_UNAVAILABLE`: the policy stores no Claude discovery binding.
1259
+ - `BINDING_INVALID`: the stored binding is not a Claude binding with a
1260
+ `reviewed` or `automatic` method, or its sources, directories or root
1261
+ aliases do not match the binding schema.
1262
+ - `POLICY_CHANGED`: the policy changed while it was being verified.
1263
+ - `DISCOVERY_BINDING_REJECTED`: any other failure.
1264
+
1265
+ The operation is read-only. It reads the policy and the stored witness paths
1266
+ and directories. It writes no file, cache, selection pin or policy, and it does
1267
+ not start Claude or change native state.
1268
+
911
1269
  ## Profiles, station sync and rollback
912
1270
 
913
1271
  ```bash
@@ -1009,7 +1367,10 @@ hosted publication access.
1009
1367
  `--profile` option chooses an isolated credential file; these are separate
1010
1368
  settings. `HASNA_SKILLS_SELECTION_PROFILE` overrides the installed selection
1011
1369
  profile. A project lock and an existing session keep exact versions until they
1012
- are explicitly changed or a new session starts.
1370
+ are explicitly changed or a new session starts. The one exception is the
1371
+ managed hook's automatic migration of an expired session pin described above.
1372
+ It never applies to a session created from a project lock, and never while a
1373
+ lock sits in the prompt's working directory or an ancestor.
1013
1374
 
1014
1375
  ## Executable skills
1015
1376
 
@@ -1367,7 +1728,7 @@ of app folders, and `XDG_CONFIG_HOME` is not consulted at all.
1367
1728
  | `skills setup-info` | | Version, pinned skills, agent configs, paths |
1368
1729
  | `skills export` | | Export pinned skills as JSON |
1369
1730
  | `skills import <file>` | | Pin skills from a JSON export |
1370
- | `skills config set <key> <value>` | | Set default agent, scope, output format, or API origin |
1731
+ | `skills config set <key> <value>` | | Set default agent, scope, output format, or API origin; `sessionPinMigration off --global` keeps managed hooks from migrating refused session pins (global scope only) |
1371
1732
  | `skills config unset <key>` | | Remove a configuration value (`skills config unset apiUrl` clears the stored service address and returns to running on this machine) |
1372
1733
  | `skills new <name>` | `scaffold` | Scaffold a portable skill under `~/.hasna/skills/installed/<name>` |
1373
1734
  | `skills port <path>` | `add` | Import an existing skill folder into the portable standard |
@@ -2746,6 +3107,17 @@ Review the plan before adding `--apply`; the original review preimage must match
2746
3107
  its original mode, and unrelated changes refuse. This preserves the native
2747
3108
  configuration, root aliases, other witnesses and active session pins.
2748
3109
 
3110
+ Verifying a typed Codex settings witness from `codex-settings-v1` to
3111
+ `codex-settings-v5` also accepts the one write Codex 0.162.1 makes on its own
3112
+ first TUI or desktop launch: the boolean `tui.screen_reader_detection_done`
3113
+ receipt, added, removed or flipped (V6 already omits it). No digest changes
3114
+ meaning, and verification alone records or rebinds nothing. A non-boolean receipt, any other TUI key
3115
+ (including the `animations = false` Codex adds when it detects a screen
3116
+ reader) and every other change still drift, including Codex's
3117
+ `[projects."<dir>"]` trust entries. A planned Skills write to `config.toml`
3118
+ rebinds the witness to the written document, which adopts at most that
3119
+ receipt.
3120
+
2749
3121
  `claude-settings-v4` is an explicit successor to `claude-settings-v3`. It
2750
3122
  additionally ignores only a top-level `theme` whose value is exactly one of the
2751
3123
  documented built-in values (`auto`, `dark`, `light`, `dark-daltonized`,