# Reviewed plugin admission

`skills integration plugin` prepares a plugin before a coding agent discovers
it. Projection contracts support schema versions 1 and 2; targets use schema
version 1, bindings use schema version 2, and admission plans and receipts use
schema version 3. Claude command sources use `copy` mode, certified against
Claude 2.1.274 and 2.1.276 on Linux ARM64. It removes native skill and command prompts while preserving
reviewed agents, tools, MCP, LSP, hooks and assets. Prompt hooks enforce drift;
they do not clean an already loaded plugin catalog.

All packages and migration mappings belong in the owner's private Skills
instance. The public Skills package ships this software and synthetic tests;
it contains no operational plugin catalog or skill payloads. The Skills server
can use its supported storage backend. Plugin admission does not require S3.

## Private package contract

Publish migrated prompts through the normal versioned Skills authoring flow.
Then publish a separate instruction bundle containing `plugin-projection.json`
and the original regular-file plugin tree under `original/`. Keep its root
instruction and package metadata outside that tree. Select the integration
bundle and all its mapped payload versions in a dedicated integration profile.
These operations do not silently publish or change a selection profile.

The exported `PluginProjectionManifest` retains this strict version 1 contract:

| Field | Meaning |
| --- | --- |
| `schemaVersion`, `agent` | `1`, `"claude"` |
| `pluginId` | Exact `plugin-name@marketplace-name` |
| `upstream` | Credential-free HTTPS source, revision, version, license, and original `treeDigest` |
| `review.hooks` | `"reviewed-no-skill-injection"` |
| `review.dependencies` | `"reviewed-no-retired-payload-dependency"` |
| `payloads` | Every original skill and command prompt, with source path, kind, source digest and exact hosted target slug/version/bundle digest |

Schema version 2 also supports original manifests that omit `version`. It uses
`upstream.version: null`, requires a full lowercase 40- or 64-character Git
commit in `upstream.revision`, and still binds the complete original tree digest.
A null version refuses an original manifest with any `version` field, including
null or an empty string. A declared version must still match exactly. No vendor
version is invented or written into the projection. Claude's command-copy cache
uses a bare 12-character content hash when the manifest has no version; declared
versions retain their version-plus-hash form. Both forms require the exact
admitted producer receipt and complete file witnesses.

Version 2 requires `review.documentation`, an array containing zero or one
`{ path, sourceDigest }` entries. Only an exact root `README.md` (case-insensitive
spelling), with mode 0644 and its one-file tree digest, can be declared inert
documentation. Its original bytes may describe a retired prompt. The exception
refuses a README selected by a native component, referenced by native configuration,
or referenced by another retained text file. Other Markdown, including agents,
keeps the strict removed-file and native skill-preload checks. An undeclared README
also keeps those checks. This explicit review does not certify arbitrary dynamic
runtime behavior: dependency review must still establish that the README is not
an indirect runtime input. Version 1 gains no documentation exception.

`pluginTreeDigest(entries)` hashes sorted file witnesses: path, normalized mode,
size and SHA-256. A payload's `sourceDigest` is that function applied to its
single original file. The source is preserved byte-for-byte in the private
archive; the native projection is derived from it. Default and custom command
paths, dormant default commands, root skills and case-insensitive aliases are
covered. Custom skill trees are removed, including their support files. The
only rewritten ordinary file is the plugin manifest when its `skills` or
`commands` declarations need removal.

Overlapping ordinary components, runtime references to removed files, native agent
skill preloads, unsupported manifest fields and upstream package installation
requirements refuse admission. Review must cover indirect/dynamic dependencies
as well as the direct references checked by software. Admission never executes
an upstream shell command or installation script. Preserved plugin tools and
hooks still run under the native agent's normal trust controls when used.

## Plan, admit and resolve

Prepare an owner-only JSON `PluginAdmissionTarget` with schema version 1,
plugin ID, exact `registrations` (user and/or canonical project paths), certified
native executable/version/digest, and the absolute Skills executable/digest.
One marketplace command serves that complete reviewed registration set.
Executable digests use `sha256:<hex>`; symlink aliases must be resolved to their
reviewed canonical file paths. No executable is invoked while planning.

```sh
skills integration plugin plan plugin-container --selection-profile integrations --target /absolute/private/target.json
skills integration plugin admit plugin-container --selection-profile integrations --target /absolute/private/target.json --plan-digest sha256:REVIEWED_DIGEST --evidence-digest sha256:REVIEWED_EVIDENCE
skills integration plugin resolve --binding REVIEWED_BINDING_ID
```

Plan and admit emit JSON. Review the exact plan digest, retained/removed file
witnesses, provenance and hosted payload mappings. Admit refetches the profile
and exact bundles before accepting that digest. Its owner-only immutable receipt
binds authority, workspace, profile ID, exact canonical container and mapped
payload versions/digests, source and projection digests, scope set, executable
witnesses, resolver command, authenticated owner identity, exact profile
revision, aliases and triggers. `observation` records the same freshly observed
principal and routing state. `evidenceDigest` hashes that complete authenticated
evidence snapshot, and `planDigest` hashes the admission identity including the
evidence digest. Admission requires both reviewed digests. Receipt reads validate
both hashes, strict field schemas and agreement between mapped and observed
identities. Legacy binding, plan and receipt schemas are refused rather than
upgraded implicitly. Originals remain in the
private hosted bundle; local native materializations contain only the projection.

The owner-local store is `~/.hasna/skills/plugin-admission/`: `bindings/` holds
content-addressed bindings, `receipts/<binding>/` holds approved plans, and
`objects/<binding>/<plan-digest>/` holds complete immutable projections. The
resolver freshly authenticates through normal owner credential configuration
on every call, including exact payload bundle reads. It refuses environment
authority/local-storage overrides. No `--cached`, API URL/key or local fallback
option exists. The resolver calls authenticated `whoami` before and after profile,
bundle and executable verification. The stable user/account identity and required
`owner` role must remain unchanged, and the account must equal the selected
workspace. Credential rotation remains valid because receipts never bind a raw
API-key identifier. Any profile revision, alias or trigger change requires a new
plan and explicit approval, including edits made after review. Missing
canonical selections, changed versions/digests, authority/workspace/profile
changes, revoked API access, changed package content or executable witnesses
still refuse or require renewed admission. A mapped canonical slug cannot be
replaced through an alias, even with identical content. Human plan/admit input
may use an integration alias; the resulting binding uses its canonical slug.

Re-admitting an unchanged identity and evidence returns its original receipt.
A new plan reports current principal and routing evidence. Resolver calls remain
write-free. Canonical object keys and
registration-set ordering keep binding IDs, plan digests and persisted binding
bytes stable.

Configure the private marketplace source using the receipt's exact
`sourceCommand`, `source: "command"`, `mode: "copy"` and `timeout: 30`.
On certified Linux hosts, that accepted command opens the reviewed resolver,
hashes `/proc/self/fd/9` with the absolute system SHA-256 utility, and executes
the same pinned descriptor. A pathname replacement before the open is rejected;
a replacement after hashing cannot become the executed resolver. The resolver
has a 25-second API deadline and prints exactly one absolute
directory path on success. Errors produce sanitized stderr and a nonzero exit.
Only explicit admission writes artifacts; resolve cannot publish or materialize
an unapproved revision. Concurrent admission uses a nonwaiting publication lock.
Interrupted attempts cannot expose partial directories as successful results.

Registration and activation remain explicit operations. Review the command
shown by Claude's JSON install/update response and pass its exact
`--accept-command` hash; never substitute blanket approval. Command-source
support begins at 2.1.229 and exact CLI acceptance at 2.1.271, but version 1
certifies 2.1.274 and 2.1.276. Claude invokes the source during installation and updates;
this does not imply a resolver invocation on every agent startup. This command does not upgrade Claude, register a
marketplace, change native settings, disable other plugins or restart agents.

## Discovery transition contract

After explicit native registration, use `captureManagedPluginRegistry()` in a
reviewed discovery input. It emits a `claude-plugin-registry` source witness with
the registry path and exact admission bindings. Keep the normal full witnesses
for native settings, marketplace catalog/source configuration and other loader
inputs. Do not retain a competing whole-registry byte witness when intentionally
admitting receipt-backed transitions; unmanaged entries remain covered by the
structured registry witness itself.

Only these managed row fields can vary after validation: `version`,
`installPath`, `sourceProducerPath`, `previousProducerPaths`, `lastUpdated`.
Every current row must match its own producer receipt and exact reviewed scope.
All retained native cache versions must match approved immutable projections,
including their command files, ordinary components, paths, modes and membership.
The certified native adapter separately validates root `.in_use/<pid>` process
markers and `.orphaned_at` epoch-millisecond pruning markers. These bounded
metadata records cannot authorize content, and cannot come from the original
package. Links, unknown keys, payload files and nested directories still refuse.
Every unmanaged row and every unknown field remains in the witness. New
registrations, changed commands, cross-scope substitutions or modified caches
refuse. Old receipts and projections remain until separately reviewed retirement.

Project plugin enablement requires a full reviewed settings-file witness and
matching admitted project registration. Other project discovery overrides and
new loaders still require their dedicated review. API failure refuses an update; it does not report a
stale projection as synchronized. Claude may retain its previous installation
after a refused update, whose existing local drift checks continue to apply.

## Prospective Claude candidate review

Before adding or changing a marketplace, an installer may call
`skills hook review-claude-candidate --request <absolute-json-path> --json`.
The `skills.claude-plugin-prospective-review/v1` request binds one frozen
marketplace/catalog closure and a non-empty `plugins` array. Each selected
plugin supplies its exact `plugin`, `manifestPath`, `manifestSha256` and bounded
JSON `optionsPatch`; an optional `releaseManifest` carries the separate
release.json path and raw-byte SHA-256. The request also binds current Skills
and Claude preimages, explicit user/project scope, caller assessment and one
complete expected semantic document set for the whole selected plugin batch.
The validator checks every selected manifest and source against the same
closure before returning one receipt. It derives the owned changes and refuses
any unrelated graph change. Harnesses validates each options patch against its
release manifest before invoking Claude; Skills does not copy its private
option allowlists or infer that caller-supplied options are release-approved.
The supported catalog shape uses relative selected-plugin sources inside that
closure. Nontrivial `metadata.pluginRoot` values and native catalog or selected
plugin dependency declarations refuse because this validator does not resolve
or review unselected external plugins.
The request may declare `functionHooksEnv: "1"`; without that declaration the
environment document is preserved unchanged. For a measured Claude 2.1.276,
2.1.278, 2.1.282, 2.1.287, 2.1.293, 2.1.296, 2.1.299, 2.1.300, 2.1.301, 2.1.302,
2.1.314, 2.1.316, 2.1.317, 2.1.318 or 2.1.319 user-scoped install,
`candidate.settingsMarketplaceSource: "directory"`
explicitly declares the native settings registration. Skills derives
`extraKnownMarketplaces[candidate.marketplace].source` from the exact frozen
candidate root. The measured operation accepts existing directory sources
only. Foreign entries may also carry a boolean `autoUpdate`; other metadata
refuses before writing because the native parser drops unknown fields.
Selected-entry metadata, including `autoUpdate`, also refuses because this
Claude release replaces that entire entry. The complete supplied settings
document must match the derived projection, preserving all foreign entries.
The same declared operation preflights every authoritative
`known_marketplaces.json` row: exact `source`, `installLocation`, `lastUpdated`
and optional boolean `autoUpdate`. Sources are either `{source: "directory",
path: <normalized absolute path>}` or `{source: "github", repo: "owner/repository"}`.
Other source keys and row metadata refuse before the native parser can drop them.
The complete `installed_plugins.json` document must contain only `version` and
`plugins`. Every existing row, including untouched plugins and other scopes,
must have `scope`, `installPath`, `version`, `installedAt` and `lastUpdated`,
with a project path for project/local scope and an optional 40-character
lowercase hexadecimal `gitCommitSha`. Unsupported metadata or malformed fields
refuse before installation; supported foreign rows remain exact in the required
after-document. Existing selected-row restrictions still apply.
Omission preserves the settings field unchanged; other values, versions and project scope
refuse. The installer must still verify the actual native executable and
poststate; a supplied version string does not establish consumer identity.
The validator shallow-merges
each declared options patch into the selected plugin's existing `options`;
the patch must explicitly bind `enabled: true`, and the validator enforces that
value after merging. This preserves all other selected options and unrelated
plugin configuration. It refuses a selected `enabledPlugins` array shape that
this schema does not model. Existing target registration rows keep their array
positions, new rows append, and unrelated rows remain byte-semantically bound.
Its receipt binds the raw request bytes and the complete projected documents;
semantic digests use UTF-8 JSON with object keys recursively sorted in
ECMAScript UTF-16 order, array order retained and JavaScript JSON primitive
serialization.

Native-generated fields use typed markers only in the exact selected rows:
`{"$generated":"claude-utc-timestamp","field":"<path>"}` requires a valid generated
UTC timestamp at that field. For a native update that may preserve or refresh
`lastUpdated`, `{"$allowed":[<exact prior timestamp>,<generated marker>]}` permits
only those two values. Existing `installedAt` remains exact; a fresh install's
`installedAt` is generated. No other row fields accept markers.

This is a read-only validator. It does not establish that caller-supplied
`native.home`, config/plugin roots, executable identity or Skills `dataDir` are
the installed consumers. Harnesses must resolve them through the installed
package-owned consumers and compare them before trusting the receipt. It must
also retain its existing review record and bind it to the exact candidate:
the caller's no-skill-injection assessment is not a semantic hook review, and
structural refusal checks do not prove hook behavior. Recheck all preimages and
candidate closure immediately before the first native mutation. The validator
does not claim native install atomicity or crash recovery.

The reviewed discovery inputs must bind every current Claude settings and
registration source: the user settings, both project settings files,
`known_marketplaces.json` and `installed_plugins.json`. A project
`.claude/settings.json` or `.claude/settings.local.json` that the reviewed
sources omit is exempt only when no other expected source (the target settings
or the user settings) shares its path, and it is either:

- absent, with a null preimage; or
- present, and all of the following hold:
  - its current bytes match its preimage SHA-256; a mismatch refuses with
    `<name> preimage changed`;
  - those exact bytes are strict UTF-8 without a byte-order mark and parse as
    one JSON object within the native JSON witness limits (no comments,
    trailing commas, trailing content or duplicate keys at any depth);
  - none of `enabledPlugins`, `extraKnownMarketplaces`,
    `additionalMarketplaces` or `skillOverrides` is present at any value,
    including `null`, `{}` and `[]`;
  - every top-level key is on the exemption allowlist with its type:
    `permissions` (an object), `outputStyle`, `model`, `theme` and
    `effortLevel` (strings), and `verbose` and `spinnerTipsEnabled` (booleans);
  - `permissions` holds only `allow`, `deny` and `ask` (arrays of strings) and
    `defaultMode` (a string).

Anything else keeps the file required, including `env` (Claude reads
`CLAUDE_CODE_PLUGIN_DIRS` from it), `hooks`, `permissions.additionalDirectories`
(Claude loads skills, commands and agents from those directories), `__proto__`
and unknown keys. The allowlist names settings reviewed as not configuring
plugins, marketplaces, skills, hooks, extra directories or the environment. It
does not prove that Claude cannot discover anything from the file; a setting
joins it only after that review.

This covers the permissions-only `~/.claude/settings.local.json` that Claude
writes when it saves a permission rule during a user-scope install, whose
project path is the Claude home. A present unreviewed file that fails the parse,
discovery-key or allowlist checks refuses and names the reason. Each exempt file
is re-checked against its exact preimage (still absent, or the same bytes) after
discovery and again before the result, so a change, creation or removal in
between refuses. The live hooks' project check still runs on top; it refuses
any of the four discovery keys (`additionalMarketplaces` included, because
Claude 2.1.300 rewrites it into `extraKnownMarketplaces` on every settings read)
and its other project skill and hook settings. The user settings and both
registries are never exempt.

The optional release manifest witness binds the complete supplied release.json
file bytes, path and SHA-256 in the request/receipt. Harnesses remains
responsible for verifying that its release options correspond to every
selected `optionsPatch`; the public Skills validator deliberately does not
interpret private release schemas.
Installed consumers can use the public `captureClaudeProspectiveCandidateClosure`
and `hashClaudePluginManifest` exports to prepare the exact digests without
copying the package's hashing algorithms.

### Claude installer witness CLI

The installed Skills command exposes a fixed read-only bridge for installer
evidence:

```text
skills hook capture-claude-installer --request /absolute/request.json --json
```

The UTF-8 request is a bounded JSON object with schema
`skills.claude-installer-capture/v1`, the exact configured `commandPath`, one
fixed `operation` (`settings`, `marketplace-registry`, `managed-plugin-registry`,
`path-bytes`, `discovery-directories`, `plugin-manifest`, `candidate`, or
`discovery-binding`), and only that operation's documented inputs. The
`discovery-binding` operation verifies the stored Claude discovery binding as a
whole, including witnesses retained under an absent retired plugin-cache
version; its request and result contract is in the README section
"Claude installer witness CLI". The result uses
`skills.claude-installer-capture-result/v1` and contains a Skills producer
receipt plus the selected existing witness result. Candidate manifests include
their plugin name, canonical path, `hashMode: "claude-plugin-manifest-v1"`, and
SHA-256; the closure and discovery digests use the existing Skills algorithms.
The command verifies the configured command resolves to this installed
`@hasna/skills` producer and rechecks producer identity around capture.

This command only captures and validates source witnesses. It does not invoke
Claude, install plugins, mutate settings or policy, authorize a candidate, or
establish installer atomicity or crash recovery. Input refusals identify the
fixed field or operation and its accepted form without echoing request contents.

## Reviewed plugin-hook attestation scope

A reviewed discovery input carries `pluginHooks: "reviewed-no-skill-injection"`.
That value is the reviewer's statement; Skills does not inspect hook behavior.
What the software currently enforces is narrower:

- It is point-in-time. It binds the listed sources as they were when the review
  was applied, apart from the manifest projection described here and the
  retired-root exemption described below. A manifest listed as a plain byte
  witness with a non-null `sha256` is stored as a `claude-plugin-manifest-v1`
  projection that ignores the descriptive fields `description`, `version`,
  `author`, `homepage`, `repository`, `license` and `keywords`. The projection
  also normalises whitespace and JSON string escaping; key order stays bound.
  Every other manifest field, including `hooks`, `skills`, `commands` and
  unknown fields, stays bound.
- It enforces one coupling only, and only for a Claude
  `.claude-plugin/plugin.json` listed as a plain byte witness (`hashMode`
  omitted or `"bytes"`) with a non-null `sha256`. The review must then also list
  that plugin's `hooks/hooks.json` (when the file exists at review time) and any
  string `hooks` target in the manifest, each as a plain byte witness with a
  non-null hash. A non-string `hooks` value and a `hooks` target outside the
  plugin root are refused. A manifest listed as `claude-plugin-manifest-v1`
  (the form Skills writes into the stored policy) or as `path-bytes` triggers
  no hook-file requirement, and neither does a manifest the absent retired
  version rule below retains. Nothing else is required.
- A `hooks/hooks.json` that is absent at review time and appears later is
  detected if the review pins it with `sha256: null` or covers it with a
  reviewed `directories` membership witness on the plugin root or its `hooks/`
  directory. Such a witness, from `captureDiscoveryDirectories(paths)`, binds
  the recursive path and type of every member, so every check detects added or
  removed files and directories under it, but not content changes.
- For plugins not covered by a `claude-plugin-registry` admission witness (see
  [Discovery transition contract](#discovery-transition-contract)):
  - other plugin files, such as hook modules (`register.ts`, `runtime.ts` and
    their imports), `bin/` files and `package.json`, are bound only if the
    review lists them. A content change to an unlisted file is not detected,
    and an added or removed file is detected only under a reviewed
    `directories` witness that covers it;
  - the review is not anchored to the roots registered in
    `~/.claude/plugins/installed_plugins.json`. When a review is present, the
    enabled-plugin walk does not run, so Skills does not check that the listed
    plugin roots are the registered ones or that every enabled plugin is listed.
- A `claude-plugin-registry` witness covers its admitted, receipt-backed
  plugins on every check. It re-reads `installed_plugins.json`, requires each
  managed row's `installPath` to be `cache/<marketplace>/<plugin>/<version>`,
  checks every file in every retained cache version against the admission
  receipts without the review listing them, and binds unmanaged rows exactly.
- Claude rewrites the 13-digit millisecond timestamp in
  `.claude/plugins/cache/<marketplace>/<plugin>/<version>/.orphaned_at` when it
  starts. Capturing that exact path with `captureDiscoveryByteSources` or
  `captureDiscoveryPathSources` (including the installer `path-bytes`
  operation) produces a `claude-plugin-orphaned-at-v1` witness, and a fresh
  explicit review converts a plain byte or `path-bytes` witness of it to that
  form, unless the absent retired version rule below retains it. The witness binds the marker's presence or absence, not its bytes or
  inode. Every capture and check still requires a regular, non-symlink file
  owned by the current user, at most 32 bytes, matching `^[1-9][0-9]{12}$`.
  The name stays in `directories` membership, so other added or removed names
  are still detected. A marker at any other depth, any other name, and any
  Skills-owned producer tree keep their exact byte witnesses. A stored review
  that binds the marker's bytes keeps that meaning and needs one fresh review.
- An entirely absent retired Claude version root exempts only its plain byte
  sources (`hashMode` omitted or `"bytes"`, with no `format`, `fields` or
  `managedPlugins`), its `.claude-plugin/plugin.json` as a
  `claude-plugin-manifest-v1` witness and a present
  `claude-plugin-orphaned-at-v1` marker. Such a source with a non-null
  `sha256` is also the only
  thing that identifies the root, and the root qualifies only while it stays
  entirely absent and the reviewed settings and registry select a different,
  verified version. Every other source under that root is re-hashed as usual,
  and one whose file existed at review no longer matches its hash, so the
  check refuses. A `directories` witness that covers the retired root is not
  exempt either. An explicit review (`--discovery-inputs`) keeps each exempt
  source exactly as listed: it does not project a byte-witnessed manifest,
  require its hook files or convert a byte-witnessed marker, so a historical
  byte witness keeps its meaning. If the root reappears, its sources are
  checked again.
- It is detection-only. Later checks re-hash the stored sources and re-walk any
  reviewed `directories` witnesses. Drift makes the live
  Skills hooks report `NATIVE_SKILL_DRIFT` (a warning, the Skills context
  omitted for that turn and a drift receipt) without blocking the prompt;
  install, trust and migration refuse it. It does not stop, unload or sandbox
  plugin code, which the native client keeps running.
- It is a tripwire, not a security boundary. A process running as the same user
  can rewrite the plugin files and the managed policy that holds the witnesses.

For plugins outside receipt-backed admission, a stronger witness anchored to the
registered plugin roots is planned and not yet implemented.

## Marketplace registry timestamps

For an explicitly reviewed `known_marketplaces.json`,
`captureClaudeMarketplaceRegistry(path)` emits a separate
`claude-marketplace-registry` witness. It preserves every marketplace name,
source, install location and unknown value. Only a valid UTC `lastUpdated`
timestamp can vary, and only for exact rows containing `source`,
`installLocation` and `lastUpdated`, with a recognized GitHub repository or local
directory source. Rows with any extra field, including `autoUpdate`, retain
their entire contents in the digest. Registration changes still refuse.

This mode is opt-in for reviewed Claude discovery. It cannot project fields,
carry managed-plugin rules, accept an absent registry or synthesize registry
changes during hook installation. It does not replace the separate settings,
installed-plugin, marketplace catalog, loader, root or payload witnesses.
Capture requires a bounded regular file with strict UTF-8 and unambiguous JSON;
links, duplicate keys and concurrent file replacement refuse.

Existing byte witnesses retain their exact behavior. A prior hash mismatch is
not proof of a timestamp-only change: review the complete current registration
and its complementary sources before explicitly replacing an old witness.
Capturing this witness does not write a policy or approve native registration.

## Marketplace plugin entry witness

`claude-marketplace-entry-v1` witnesses one plugin entry of a Claude
`marketplace.json`, for a plugin whose only declaration is its marketplace entry
(for example a `strict: false` entry with no `plugin.json`, such as
`swift-lsp@claude-plugins-official`). A bytes witness of the whole catalog
drifts every time Claude refreshes the marketplace by itself; this mode moves
only when something that decides how the selected entry resolves or what it
injects changes.

The source names an absolute, normalized `<root>/.claude-plugin/marketplace.json`
path, the exact marketplace `name` and the exact plugin entry `name`:

```json
{ "path": "/home/user/.claude/plugins/marketplaces/claude-plugins-official/.claude-plugin/marketplace.json",
  "hashMode": "claude-marketplace-entry-v1", "marketplace": "claude-plugins-official",
  "plugin": "swift-lsp", "sha256": "<digest>" }
```

It is accepted only in explicit reviewed Claude discovery. Paths with `..`,
`//`, a trailing slash, control characters, another file name or another parent
directory refuse, as do names outside `[A-Za-z0-9][A-Za-z0-9._-]{0,127}`.

The digest is SHA-256 over the domain prefix
`hasna.skills.claude-marketplace-entry.v1\0` and canonical JSON (sorted object
keys, array order and number spelling kept, no whitespace) of:

- the marketplace `name`, `metadata.pluginRoot` (`null` when absent) and
  `allowCrossMarketplaceDependenciesOn` (`null` when absent), for every entry
  whether or not it declares `dependencies`: the root allowlist also governs
  dependencies declared in the plugin's own `plugin.json`, which Claude's
  auto-update and `/reload-plugins` act on. A present allowlist must be an
  array of strings, so `null` only ever means absent;
- the selected entry projected to its bound keys: `name`, `source`, `strict`,
  `defaultEnabled`, `dependencies`, `relevance`, `headers`, `headersHelper`,
  `settings`, `userConfig`, `types`, `channels`, `skills`, `commands`, `agents`,
  `hooks`, `mcpServers`, `lspServers`, `outputStyles`, `workflows`,
  `experimental`, `themes` and `monitors`, each with its full value.

Key order: this canonical JSON sorts object keys at every level, under the
domain prefix above. That deliberately differs from `claude-plugin-manifest-v1`,
which keeps the manifest's own key order and has no domain prefix. Reordering
keys in the catalog therefore never moves this digest, while array order,
including the order of `allowCrossMarketplaceDependenciesOn`, stays bound.

The entry's display and catalog metadata is validated and omitted:
`$schema`, `description`, `version`, `author`, `homepage`, `repository`,
`license`, `keywords`, `category`, `tags`, `displayName` and `metadata`. A real
version or install-path change is still caught by the separate
`installed_plugins.json` witness, which this mode never replaces. Other entries,
the catalog `description`, `version`, `owner` and `$schema`,
`forceRemoveDeletedPlugins` and renames of other plugins do not move the digest.
Adding, removing or changing `allowCrossMarketplaceDependenciesOn` always does.

The mode fails closed. Capture and verification refuse when:

- the file is missing, a link, a special file, larger than 1 MiB, not strict
  UTF-8, not one JSON object, has trailing content or a duplicate key;
- the marketplace `name` differs from the bound name, or `plugins` is not an
  array;
- the selected entry is missing, appears twice, or has a case-only variant;
- `renames` maps the selected name to another entry or to `null`;
- the entry has a key outside the two lists above (including the directory
  listing fields), `experimental` has a key other than `themes`, `monitors` or
  `evals`, or an object `source` has an unknown type or a field outside that
  type's documented fields (a `url` source may also carry `path`, as the
  official catalog does);
- the catalog has a top-level key outside `$schema`, `name`, `owner`,
  `plugins`, `description`, `version`, `metadata`, `forceRemoveDeletedPlugins`,
  `allowCrossMarketplaceDependenciesOn` and `renames`, or a `metadata` key other
  than `description`, `version` and `pluginRoot`;
- an omitted metadata field has the wrong type.

New fields are refused, never ignored, until a reviewed version of this mode
covers them. A refusal for an unknown key names the mode, the bound marketplace
name and the exact key path, and never echoes a value. Key names are bounded to
64 characters and JSON-quoted, with every character outside printable ASCII
escaped. A plain key extends the path with `.key`; any other key appears as
`["key"]`. For example:

```text
claude-marketplace-entry-v1: unknown top-level key "pluginSearchPaths" in claude-plugins-official
claude-marketplace-entry-v1: unknown key "metadata.skillRoot" in claude-plugins-official
claude-marketplace-entry-v1: unknown key "plugins[swift-lsp].futureInjector" in claude-plugins-official
claude-marketplace-entry-v1: unknown key "plugins[swift-lsp].source.script" in claude-plugins-official
```

Inside a hook check the same text follows `NATIVE_SKILL_DRIFT:`. The only way
forward after such a refusal is a fresh human review of the changed catalog
and a guarded exact re-pin through hook installation: write the reviewed
witnesses to a discovery inputs file, preview `skills hook install --agent
claude --discovery-inputs <file>`, then run the same command with `--apply`.
While the unknown key is present this mode refuses capture too, so that review
must bind the catalog another way, for example an exact `bytes` witness from
`captureDiscoveryByteSources`, until a reviewed version of this mode covers the
key. There is no bypass, ignore list or relaxed mode.

The field lists follow the
[marketplace reference](https://code.claude.com/docs/en/plugins/marketplace-reference)
and the [plugin manifest reference](https://code.claude.com/docs/en/plugins/manifest-reference),
read on 2026-10-07.

Capture a witness with
`skills hook witness --kind claude-marketplace-entry-v1 --path <marketplace.json>
--marketplace <name> --plugin <name> --json` (or
`captureClaudeMarketplaceEntry(path, marketplace, plugin)`). Capture writes
nothing and does not authorize a review. Put the witness in reviewed discovery
inputs, keep the `settings.json`, `installed_plugins.json` and
`known_marketplaces.json` witnesses and the plugin's absence witnesses, and
preview `skills hook install --discovery-inputs <file>` before applying. Hook
installation never rewrites the catalog, so an entry change between planning
and applying refuses.

Install a runtime that recognizes `claude-marketplace-entry-v1`, including any
bundled copy of the verifier, before a policy carries it; an older runtime
refuses the whole policy because the hash mode is unknown.

## Claude settings preferences

`captureClaudeSettings(canonicalSettingsPath)` returns a versioned
`claude-settings-v1` discovery source for the configured Claude user
`settings.json`. Use the canonical target when a reviewed home root alias is
present. The mode is accepted only in explicit reviewed Claude discovery; it
cannot stand in for a plugin manifest, executable, project settings review or
another agent's configuration.

The digest canonicalizes JSON object key order, preserves array order and
unknown numeric values without rounding, and excludes only these top-level
preferences and model selections with valid documented types:

- Booleans: `autoScrollEnabled`, `axScreenReader`, `emojiCompletionEnabled`,
  `prefersReducedMotion`, `showTurnDuration`, `spinnerTipsEnabled`,
  `syntaxHighlightingDisabled`, `terminalProgressBarEnabled`,
  `terminalTitleFromRename`, `verbose`, `wheelScrollAccelerationEnabled`.
- Exact strings: `editorMode` (`normal`, `vim`), `tui` (`default`, `fullscreen`),
  `viewMode` (`default`, `verbose`, `focus`).
- Built-in `model` values: `default`, `best`, `fable`, `fable[1m]`, `sonnet`,
  `sonnet[1m]`, `opus`, `opus[1m]`, `haiku`, `opusplan`; exact model IDs
  `claude-fable-5-1`, `claude-fable-5`, `claude-fable-5[1m]`, `claude-opus-5`,
  `claude-sonnet-5`, `claude-haiku-4-5-20251001`, `claude-opus-4-6`,
  `claude-sonnet-4-5`, `claude-sonnet-4-5-20250929`, `claude-opus-4-8`,
  `claude-opus-4-8[1m]`, `claude-opus-4-7`, `claude-sonnet-4-6`,
  `claude-opus-4-5-20251101`, `claude-opus-4-5`, `claude-haiku-4-5`,
  `claude-fable-5-1[1m]`, `claude-opus-5[1m]`, `claude-opus-4-7[1m]`,
  `claude-opus-4-6[1m]`, `claude-sonnet-4-6[1m]`.

The model exception follows the documented [model picker](https://code.claude.com/docs/en/model-config#setting-your-model)
and [model IDs](https://platform.claude.com/docs/en/models/overview), including
the documented [extended-context model-name suffix](https://code.claude.com/docs/en/model-config#extended-context)
only for the listed supported combinations. It permits
inference selection among this fixed list, including clearing the override. It
does not exempt provider or alias mappings, `modelOverrides`, `modelPicker`,
`modelSettings`, `availableModels`, environment or model-switch hooks. Arbitrary
custom, provider-specific, future or path-like model strings stay bound; there
is no prefix wildcard or dynamic model lookup. Non-string model values refuse.

Invalid values for the display preferences refuse capture and verification. Every
other field remains bound, including unknown future fields, hooks and their
exact commands, permissions, native skill suppression and synchronization,
plugin roots, marketplaces, environment and configuration precedence. Language,
output style, theme (v4 alone permits the built-in theme values), and
command-bearing status or file suggestion settings are intentionally retained. These are narrow preference and inference-selection
exceptions, not general permission to change Claude configuration. The native
bridge guard continues to check its exact commands and native Skill policy.

Migration requires reviewing the current complete settings and complementary
plugin/loader sources, replacing the settings raw source with the captured
typed source in a reviewed discovery-input file, and using the normal hook
installation plan/apply operation. A stale prior raw hash is not evidence that
only preferences changed. Capture performs no write or automatic refresh; old
raw witnesses remain exact. Concurrent changes during an installation still
face full-byte preconditions so a preference edit cannot be overwritten.

The source must be a stable normalized regular file named `settings.json` with
strict UTF-8, no duplicate JSON keys and no trailing content. Limits are 1 MiB,
1,024 top-level fields, depth 32, 65,536 nodes and 16,384 characters per string;
capture also consumes the shared discovery byte budget. Symlinks, special
nodes and changes during a read refuse. This witness covers the configured
user file; existing checks on project settings, root aliases and plugin sources
remain separate. It does not certify arbitrary command-line settings overrides
or guarantee when a running native client adopts a settings edit.
Those complementary checks retain their existing exact registration and hook
entry comparisons; this mode does not relax them for property-order changes.

### Explicit v4 built-in theme reviews

`claude-settings-v4` is an explicit successor to v3. It binds everything v3
binds, with one more omission: the top-level `theme` key, and only when its
value is exactly one of the built-in values `auto`, `dark`, `light`,
`dark-daltonized`, `light-daltonized`, `dark-ansi` or `light-ansi`. The list is
pinned in code (`CLAUDE_BUILTIN_THEMES`) from the
[`theme` setting reference](https://code.claude.com/docs/en/settings-reference#theme)
and the [built-in presets](https://code.claude.com/docs/en/terminal-config#match-the-color-theme),
read on 2026-10-07. Custom and plugin themes (`custom:<slug>`,
`custom:<plugin-name>:<slug>`) load theme files, so they stay bound, as does any
other value, type, spelling or letter case, and any `theme` key nested inside
another setting. `skipDangerousModePermissionPrompt`, permissions, hooks,
environment, `apiKeyHelper` and other helpers, plugins, marketplaces, custom
model values and unknown settings stay bound exactly as in v3. The digest is
domain-separated from v3.

Existing v1, v2 and v3 witnesses keep their meaning; nothing selects v4
implicitly, and a stored witness is never rewritten. A policy opts in through
either explicit path:

- a fresh review: `skills hook witness --kind claude-settings-v4 --path
  <settings.json> --json` (or `captureClaudeSettingsV4(path)`), placed in the
  reviewed discovery inputs and applied with the normal `skills hook install
  --discovery-inputs <file>` plan/apply flow;
- a preserved-preimage upgrade: `skills hook rebind-settings --agent claude
  --claude-witness-version 4 --reviewed-preimage <preserved-settings.json>
  --expected-policy-sha256 <policy-hash> --expected-settings-sha256
  <settings-hash>` (or `upgradeClaudeSettingsWitnessV4`). It accepts a raw, v1,
  v2 or v3 witness, proves the preimage matches it in its original mode, and
  refuses unless the current settings equal the preimage under v4. Without
  `--claude-witness-version` the rebind target remains v3.

Install a runtime that recognizes `claude-settings-v4`, including any bundled
copy of the verifier, before a policy carries it; an older runtime refuses the
unknown hash mode.

### Narrowed discovery reviews: `claude-settings-v5`, `codex-settings-v7` and `codex-settings-v8`

Earlier modes bind almost the whole settings file, so ordinary use tripped
discovery drift: switching to any model not on a fixed list (for example
`deepseek-flash` or `claude-opus-5-5`), an "always allow" rule, a status line,
or Codex rewriting `config.toml` for a model change, a folder trust or its own
notices. The narrowed modes bind only what can add, remove or redirect native
skills, hooks or plugins:

- `claude-settings-v5`: every top-level key whose name mentions a skill, plugin,
  hook or marketplace, in full (`hooks`, `disableAllHooks`, `enabledPlugins`,
  `extraKnownMarketplaces`, `pluginConfigs`, `disableBundledSkills`,
  `syncClaudeAiSkills` and any future key of that kind); in `permissions`, only
  `Skill` rules in `allow`/`deny`/`ask` and `additionalDirectories`; in `env`,
  only `CLAUDE_CONFIG_DIR` and variables with such a name; settings that run a
  command the way a hook does (`apiKeyHelper`, `awsAuthRefresh`,
  `awsCredentialExport`, `otelHeadersHelper`, `fileSuggestion`) and settings
  that select injected instructions (`outputStyle`, `agent`). Any model value,
  `modelSettings`, effort, other permission rules and modes, `statusLine`, other
  environment, `theme`, `tui`, view and unknown preference keys vary freely.
  There is no model allow-list. Skills-owned hook entries stay bound: `skills
  hook install` rebinds the witness when it rewrites them, and matching them by
  command text would accept a lookalike.
- `codex-settings-v7`: every root key with such a name (`hooks` without the
  native `hooks.state` trust ledger, `plugins`, `marketplaces`, `skills`
  without disable-only registrations), every `[mcp_servers.*]` table in full (a
  hook handler of type `mcp_tool` runs through a named server, so a redirected
  server `command` or `url` redirects a reviewed hook), instruction sources
  (`instructions`, `developer_instructions`, `model_instructions_file`,
  `experimental_instructions_file`, `project_doc_fallback_filenames`,
  `compact_prompt`, `experimental_compact_prompt_file` and any key naming
  instructions), every `[agents.*]` role table in full (a role's `config_file`
  loads a layer whose `developer_instructions` reach subagents, and its
  `description` is shown to the model), `[features]` flags with a skill, plugin, hook or marketplace
  name at the root and in each profile, and keys of each of those kinds inside
  `[profiles.*]`, so the `profile` selector itself can vary. Model, effort,
  verbosity, service tier, `model_provider` and `[model_providers.*]`,
  `[projects.*]` trust, `[tui]`, `[notice]`, `notify` and sandbox or approval
  settings vary freely. Because every MCP server is bound, V7 needs no hook
  dependency metadata; V6 keeps its own contract and is never narrowed.
- `codex-settings-v8`: the same projection as `codex-settings-v7` with exactly
  one scalar free — a marketplace entry's own `last_updated`, when it is a
  string or number, at the root and in every profile. Codex rewrites that
  timestamp on its own schedule, and a timestamp cannot add, remove or redirect
  a skill, hook or plugin. Every other marketplace field, the entry name, a
  non-scalar value and the table itself stay bound, so an added, removed,
  renamed or redirected marketplace still reports drift. V7 keeps its shipped
  meaning, so a receipt captured under V7 verifies exactly as before, with one
  correction: a configuration containing a profile literally named `__proto__`
  was dropped by the shipped projection and is now bound, so such a receipt
  reports drift once and needs a fresh review. V8 is a new mode for a newly
  reviewed or install-bound policy.

Deliberately unbound, and therefore not drift: `statusLine` and Codex `notify`
(the owner's request names both as ordinary use, although each runs a
command), and model routing (`env` such as `ANTHROPIC_BASE_URL`, Codex
`model_provider` and `[model_providers.*]`). Adding any MCP server to a V7
review is drift: it is reported, not blocking.

Opt in through a fresh review (`skills hook witness --kind claude-settings-v5`,
`--kind codex-settings-v7` or `--kind codex-settings-v8`) or a
preserved-preimage upgrade (`skills hook rebind-settings
--claude-witness-version 5`, `--codex-witness-version 7` or
`--codex-witness-version 8`). The upgrade accepts a raw or any earlier typed
witness except Codex V6 (V8 also accepts a preserved V7 witness), proves the
preimage in its original mode, and refuses when any narrowed field differs.

Existing Claude and Codex reviews keep verifying after an upgrade without a new
review. A legacy full-file settings witness (raw bytes,
`claude-settings-v1`–`v4`, `codex-settings-v1`–`v5`) binds everything the
narrowed projection binds, except that `codex-settings-v3`–`v5` omit ordinary
local stdio MCP servers, which V7 binds. So while it still matches, the narrowed
digest of the current file is the narrowed digest of a state that review
accepts. The live hook records that observation, computed from the same bytes
that matched, in `native-witness-narrowed.json` beside `agent-policy.json`.
New observations for a Codex legacy witness record `codex-settings-v8`, which
also frees a marketplace's own `last_updated`; an entry recorded earlier under
`codex-settings-v7` stays usable and is judged by its own recorded mode, so the
mode moving forward does not strand a station that had already drifted. When a
V7 entry already saw the same witness, V8 is recorded only while the file still
matches that V7 observation: a local stdio MCP server that `codex-settings-v3`–`v5`
omit and that was redirected in between is reported rather than adopted along
with the wider projection.
When the legacy witness later stops matching, the change is accepted only if the
current narrowed digest equals the recorded one; the first observation is never
replaced, so a local MCP server added after it is reported once the legacy
witness stops matching. Nothing is recorded from a file the legacy witness does
not verify, and an entry is bound to the exact stored witness. The same ledger
check covers the configuration witness of a Codex semantic-cache binding
(`skills hook rebind-cache`) and the settings check for retired Claude plugin
versions; the cache witness itself still compares the current disable rules.

Exceptions that still need one fresh review or an explicit
`skills hook rebind-settings` to v5/v7:

- a station that had already drifted before the upgrade (no entry is recorded
  from a file the legacy witness does not verify), for example station03, whose
  Claude model changed to `deepseek-flash` before the release;
- Codex `codex-settings-v6` witnesses and any witness with review artifacts,
  managed-plugin rules or a field projection, which are never narrowed;
- Sumi `sumi.json` witnesses (raw bytes or `sumi-settings-v1`): there is no
  narrowed Sumi projection yet, so a Sumi model or settings change is still
  drift there. It does not block, but the Skills context is omitted until the
  binding is reviewed again.

Install, trust, migrate, upgrade and portable-authoring checks stay strict and
do not consult the ledger, so after an ordinary settings change on a
legacy-witness station they still ask for a fresh review; `skills hook
rebind-settings --claude-witness-version 5` or `--codex-witness-version 7` with
the preserved preimage is the one-time repair that removes this.

### Drift reports instead of blocking

A live lifecycle hook (Claude, Codex, Gemini, Cursor, Sumi and Hermes prompt,
session, subagent and tool events) no longer blocks on `NATIVE_SKILL_DRIFT`.
For that turn it omits the Skills context, shows one bounded warning naming the
drift and the repair command, and records
`<skills-data>/native-drift/<agent>.json` (schema
`hasna.skills.native-drift-receipt.v1`, mode 0600) with the full reason, first
and last occurrence and a count. The next clean check marks the receipt
`cleared`. `skills hook drift [--agent <agent>] [--json]` lists the receipts.
New hooks, plugins and native copies are still detected and reported; they are
never adopted silently. On a live hook, a check that cannot complete (a changed
root alias, an unreadable settings file, an inventory error) is reported the
same way, as `NATIVE_SKILL_DRIFT: native discovery could not be verified: …`.

Refusals that keep blocking because they are policy, not drift:

- invoking a native payload skill instead of the `skills-cli` bridge (Claude
  `PreToolUse`, Gemini `BeforeTool`, Hermes `skill_view`/`skill_manage`);
- invoking the `skills-cli` bridge itself while it cannot be verified: the
  bridge is missing or modified, the managed binding that names it is missing
  or incompatible, its root aliases changed, or the Sumi or OpenCode bridge
  plugin or skill permissions changed. A payload written under the bridge's own
  name would otherwise load through the auto-allowed `Skill(skills-cli)` rule.
  The prompt, session or subagent still proceeds; its notice tells the agent not
  to invoke the bridge and to run the `skills` command directly;
- `DISCOVERY_ROOT_UNRESOLVED` and Sumi path-resolver refusals.

Install, trust, migrate, sync and upgrade commands still refuse drift exactly as
before.

Limit: Claude, Codex, Gemini and Cursor show the warning in their user-visible
channel (`systemMessage`, `user_message`). The Sumi and OpenCode plugins and
Hermes accept only model context on these events, so there the warning reaches
the model, not a separate user-visible line; the receipt and `skills hook drift`
are the operator's record on every host.

## Verification and limits

Unit tests cover provenance and mapping failures, offline/revoked authorities,
timeouts, concurrent publication, symlinks/special files, cache mutation, retained
history, and exact unmanaged registry coverage. Synthetic native tests exercise
actual Claude with a disposable home and loopback mock authority in a separate
network namespace. They contact no real provider and require explicit opt-in:
`SKILLS_TEST_CLAUDE_BIN`, `SKILLS_TEST_CLAUDE_SHA256` (reviewed native executable
digest), `SKILLS_TEST_CLAUDE_VERSION` (defaults to 2.1.274), and
`SKILLS_TEST_NETWORK_ISOLATED=1`. Both versioned and versionless upstream
manifests run through install, unchanged and changed updates, refusal and drift checks. Before native execution, the test
checks the source before/after copying, complete copied digest, size and ELF
format, then atomically publishes its private executable fixture.

The initial implementation is a reviewed admission boundary, not an upstream
ingestion scheduler or a general plugin execution sandbox. MacOS runtime proof,
additional native versions and providers require their own canary evidence.
Package data is bounded to 1,024 files, 64 MiB total and 16 MiB per file. Native
cache history is bounded to 128 entries and an aggregate 256 MiB per witness.

Primary runtime references: [command sources](https://code.claude.com/docs/en/plugin-marketplaces#command-sources),
[plugin components](https://code.claude.com/docs/en/plugins-reference), and
[exact command acceptance](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md#21271).

### Explicit v2 preference reviews

For new reviews, use `captureClaudeSettingsV3(path)` or
`skills hook witness --kind claude-settings-v3 --path <settings.json> --json`.
The v3 witness recognizes an entry containing only a valid `effortLevel`
independently of its model name. Claude can persist a preference for a newly
released model without blocking the next prompt. Entries with additional
properties, including `maxEffortLevel`, remain fully bound, as do provider
mappings, hooks, permissions, plugins, environment and unknown settings.
The top-level model selection rules remain unchanged.

Legacy v1 and v2 digests retain their meaning. `upgradeClaudeSettingsWitness`
accepts a legacy witness and a saved `settings.json` preimage. It verifies that
the preimage matches the old digest and that current settings differ only in
recognized preferences before returning a v3 witness. It refuses all other
drift. Use that witness in reviewed discovery inputs and apply the normal
guarded integration plan. Install compatible runtimes before activating v3;
when updating agents, verify their actual hook commands and native trust, not
only the executable found on `PATH`.

`captureClaudeSettingsV2(path)` creates a `claude-settings-v2` witness. In
addition to the v1 display/model selections, it tolerates the documented
persisted effort values `low`, `medium`, `high`, and `xhigh`, both at the top
level and in known built-in model entries whose only property is `effortLevel`.
Custom model entries and entries with any other property remain fully bound.
The boolean `skipDangerousModePermissionPrompt` remains bound: Claude also uses
this saved acceptance in bypass-mode and pending project MCP approval decisions.
Permission settings, plugins, hooks, environment, ultracode and unknown
configuration remain bound. Version 0.9.14 corrects the 0.9.13 normalization of
this flag; an existing v2 review containing it must be explicitly reviewed again.
Invalid recognized preference types are refused.

`captureClaudeMarketplaceRegistryV2(path)` creates a
`claude-marketplace-registry-v2` witness. It also recognizes a registry row with
an optional boolean `autoUpdate`. That boolean remains bound; only a strictly
validated `lastUpdated` timestamp may vary. A new source or installation path,
a changed automatic-update policy, or an unknown field still requires review.

Capture either witness through the CLI:

```sh
skills hook witness --kind claude-settings-v2 --path /canonical/home/.claude/settings.json --json
skills hook witness --kind claude-marketplace-registry-v2 --path /canonical/home/.claude/plugins/known_marketplaces.json --json
```

Capture does not install or authorize a review. Compare the current complete
configuration with its previous review, put the chosen witness in the reviewed
discovery inputs, and preview `skills hook install --discovery-inputs <file>`
before applying. Existing byte and v1 witnesses are never silently converted.
Runtime support for the selected witness version must be installed before
policy activation. A real hook installation is a configuration change, not a
preference: its owning installer must coordinate a guarded settings/policy
update, or it needs a fresh explicit review.

The effort schema follows [Claude's model configuration documentation](https://code.claude.com/docs/en/model-config#adjust-effort-level).

Cooperating installers can use `planClaudeHookEventsUpdate` with an explicit
selection of supported Claude hook events. `planClaudePreToolUseHookUpdate` and
`planClaudeStopHookUpdate` are single-event convenience wrappers. Supported events
are `PreToolUse`, `PostToolUse`, `Stop`, `Notification`, `SessionStart`, `SessionEnd`
`UserPromptSubmit` and `SubagentStart`. The transaction requires the exact settings SHA-256
preimage and preserves every field outside the selected events, including other
hooks. Apply the returned plan through `applyAgentIntegration`; this validates
existing managed discovery and native guards, writes settings and policy with
precondition checks, and retains recovery backups. An invalid policy or existing
drift is a refusal, never permission to use an uncoordinated writer. A null plan
means no managed Skills policy exists. These APIs do not authorize the hook's
behavior or turn a configuration-changing hook into a preference.

The transaction verifies every agent's discovery sources, directories and
automatic discovery closure. Native runtime and trust checks cover Claude and
the declared consumers of the settings path, including containing roots or
directories and OpenCode's Claude compatibility surface. Unrelated agents keep
their exact witnesses and trust state; they must still pass their own consumer
checks before use. The plan's `managedAgentChecks.agents` and `nativeSkills`
describe this scoped runtime check, not a fleet health result. A path-identity
witness intersecting the write still refuses instead of adopting a new inode.

## Codex bundled cleanup controls

Qualified native skill review supports a narrow subset of Codex 0.159.2,
0.160.0 and 0.160.1's bundled cleanup hooks. The identities `browser`, `chrome`, `chrome-dev`,
`chrome-internal` and `computer-use` in `openai-bundled` may call
`node_repl.turn_ended`; `unified-computer-use` may call `cua_repl.turn_ended`.
Only `Interrupt`, `SubagentStop` and `Stop` are accepted, with one group and one
MCP handler per event. Inputs are the exact native event, turn and session
placeholders; `SubagentStop` uses the child `agent_id` for its session value.
Commands, matchers, other arguments, external hook files and a plugin MCP
declaration alongside these hooks remain refused. Apps cleanup routing requires
connector identity from a native tool catalog and is not admitted by this path.

The complete inline hooks stay in the manifest fingerprint across cache versions,
including after all native skill bodies disappear. A changed hook fails guarded
apply and subsequent discovery checks. This review preserves native cleanup;
it neither enables plugins nor grants hook trust, browser authority or signing.
The upstream contracts are
[0.159.2 bundled hooks](https://github.com/openai/codex/blob/ff6aec96948b70d94983af2641a6b67c94faeff5/codex-rs/plugin/src/bundled_hooks.rs)
and [0.160.0 bundled hooks](https://github.com/openai/codex/blob/a956835d020762cb2b570053af06f643a11c0ecc/codex-rs/plugin/src/bundled_hooks.rs);
0.160.1 carries the identical file
([0.160.1 bundled hooks](https://github.com/openai/codex/blob/d27764b82f7118f674371e6d6e76271d9d606edb/codex-rs/plugin/src/bundled_hooks.rs)).
