Skip to content

feat(hooks): publish_overrides — a per-PATH policy that overrides the vault's (#10) - #14

Merged
A-PachecoT merged 3 commits into
mainfrom
sprint/publish-state
Aug 17, 2026
Merged

feat(hooks): publish_overrides — a per-PATH policy that overrides the vault's (#10)#14
A-PachecoT merged 3 commits into
mainfrom
sprint/publish-state

Conversation

@A-PachecoT

Copy link
Copy Markdown
Contributor

The hole

publish: in vault.yaml is per VAULT. The state that produces the Stop nudge is per
DOC. So when one doc cannot publish — because the server rejects it, not because
nobody tried — the only lever was publish: manual, which silences every other doc in
the vault too. What is left is a nag that fires every session with no action available,
which is how a channel gets trained into being ignored.

This repo already decided that case in the orphan bucket (stop.sh, "clear either way →
no re-nag"
). This is the missing other half.

The shape

vault.yaml gains an optional block. Absent ⇒ today's behavior, byte for byte.

name: atelier
publish: prompt
publish_overrides:
  manual:
    - BITACORA.mdx                  # why this path is listed goes right here
    - docs/context-architecture.mdx

Two properties are the point: it is explicit (someone had to type the path, and by
convention the reason beside it) and it expires by itself (delete the line when the
block lifts). No implicit suppression, ever.

Every narrowing fails toward keeping the nag:

shape behavior
exact path, relative to the vault root overrides the vault policy
a glob (* ? [ ] { } !) matches nothing — one line must not silence a subtree nobody enumerated
unrecognized key under publish_overrides: ignored; the doc keeps the vault policy
flow style {manual: [a]} reads as no overrides at all
unreadable vault, doc outside the root delegates to vault_policy()

The vault root is the directory holding vault.yaml — the same root the CLI walks up to
when it derives a slug. One rule in both places or they drift.

Structure

vault_policy() is untouched. doc_policy() sits beside it, resolves an override and
otherwise delegates — so non-regression is structural rather than a property we have to
keep testing for. stop.sh changes on one line (the bucketing) plus the comment above it.

⚠️ auto is an accepted key, so a listed path can newly reach the bucket that shells out
to basalt publish. That bucket's logic is unchanged; only which files can reach it, and
only when someone wrote the path explicitly. It has its own test, plus the twin proving an
unlisted path in the same vault is never auto-published.

Measurement

bash plugins/basalt/hooks/tests/run.shrc 0, pass 83 → 100.

Every arm is a pair: the listed path that goes silent, and a path in the same session
that must still nag. The sharpest one is #10 (b) — override lists BITACORA.mdx, session
edits it and README.mdx, must nag for exactly 1. 0 means the vault got silenced
(feature deleted); 2 means the override did nothing.

Falsifiable-instrument gate — both mutants verified by effect before their verdicts
were read:

mutant effect probe result
force doc_policy to delegate always listed=prompt (override dead) arm (a) RED — the listed doc nags; 9 arms fail
force doc_policy to return manual unlisted=manual (silence unconditional) arm (b) RED<silence> where exactly 1 was required; 35 arms fail

Also covered: all three vault policies with no publish_overrides key (unchanged),
./x.mdx normalization, a trailing # reason comment, block termination at column 0, and
both halves of the suffix trap — a bare x.mdx does not match notes/x.mdx, and an
identically-named doc in a different vault is not silenced.

bash scripts/validate-skills.sh → rc 0. The vault.yaml example in
skills/basalt/references/cli-path.md documents the new key.

Closes #10

… vault's (#10)

The publish policy is per VAULT (`publish:` in vault.yaml); the state that produces
the Stop nudge is per DOC. So when one doc cannot publish — because the server
REJECTS it, not because nobody tried — the only lever was `publish: manual`, which
silences every other doc in the vault too. What is left is a nag that fires every
session with no action available, which trains the reader to ignore the channel.
The orphan bucket already decided this case ("clear either way -> no re-nag"); this
is the missing other half.

    name: atelier
    publish: prompt
    publish_overrides:
      manual:
        - BITACORA.mdx                 # why this path is listed goes right here

Absent => today's behavior, byte for byte. Two properties are the point: it is
EXPLICIT (someone typed the path, and by convention the reason beside it) and it
EXPIRES BY ITSELF (delete the line when the block lifts). No implicit suppression.

The shape is narrow, and every narrowing fails toward KEEPING THE NAG: exact paths
relative to the VAULT ROOT (the dir holding vault.yaml — the same root the CLI walks
up to for a slug, so the two cannot drift); no globs (an entry with * ? [ ] { } !
matches nothing, because one line must not silence a subtree nobody enumerated); all
three policy words accepted as keys, an unrecognized key ignored; block style only.

`vault_policy()` is untouched. `doc_policy()` sits beside it, resolves an override
and otherwise DELEGATES — so non-regression is structural, not a thing we test for.
stop.sh changes on one line (the bucketing) plus the comment above it.

Tests: 83 -> 100 pass, rc 0. Every arm is a PAIR — the listed path that goes silent
and a path in the SAME session that must still nag — because a fix that silences
every arm has deleted the feature rather than repaired it. Both mutants killed:
forcing delegation turns arm (a) RED (the listed doc nags), forcing `manual` turns
arm (b) RED (silence where exactly 1 was required).

Closes #10
`publish_overrides:` accepts `manual` / `prompt` / `auto` and IGNORES anything else.
It did — flatly. One level deeper it did not:

    publish_overrides:
      weird:
        manual:
          - BITACORA.mdx     # silenced, under a key that was supposed to be ignored

Nothing tracked nesting depth, so the inner `manual:` opened a list as if it sat at
the top of the block. Over-accepting a key here fails toward SILENCE — a doc nobody
published stops nagging — which is the one direction this file refuses everywhere
else. A key is now only a key at the indentation of the FIRST key in the block.

Its pair goes in beside the flat unrecognized-key arm: the nested entry silences
nothing while a top-level `manual:` in the same file still works, so the arm reads
exactly 1 rather than 0. Verified load-bearing — relaxing the depth check turns that
arm RED with `<silence>`, the precise failure it exists to catch.

Tests: 100 -> 101 pass, rc 0.
…scribed

README described the policy as per-vault only, which stopped being the whole
truth when #10 landed publish_overrides. A doc that contradicts the mechanism
beside it is the same divergence class the plugin just spent an issue closing.

Refs #10
@A-PachecoT
A-PachecoT merged commit ee3734d into main Aug 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

La política de publicación es por vault pero el bloqueo es por doc: un doc que el servidor rechaza avisa en cada sesión, sin acción posible

1 participant