Skip to content

fix(cli): admit a mapping on version alone only as the policy version this build reads - #283

Merged
amondnet merged 4 commits into
mainfrom
amondnet/issue-240-version-only-admission
Sep 19, 2026
Merged

amondnet merged 4 commits into
mainfrom
amondnet/issue-240-version-only-admission

Conversation

@amondnet

@amondnet amondnet commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

What

load_policy admitted any mapping that named version, and version is the one of the four
recognised keys (version, egress, endpoints, rules) that other config formats also use. An
unquoted version: 3 at the top of a docker-compose.yml — the ordinary v2/v3-era spelling, and
exactly what a --config mistyped one file over in a deploy repository lands on — was therefore
admitted by name and loaded as a 0-rule policy.

Reproduced on the binary at 0c350c2 (current main) before the change, with a throwaway value:

file before after
version: 3 + services: with an inline POSTGRES_PASSWORD: policy is valid (0 rules, 0 endpoints), exit 0 refused, exit 1, nothing from the file in the message
version: 1 alone policy is valid (0 rules, 0 endpoints), exit 0 unchanged

Scale

The same bounds #220 recorded, and the write-up should not say more than they allow. Under
gateway --config the admitted file's whole text became AppState.policy_yaml and GET /api/policy
served it verbatim, so the compose file's inline environment values were readable there — but that
route is behind the management-token route_layer (#173), so the audience was mgmt-token /
dashboard-session holders rather than the network; and the resulting policy was egress.default: deny with no rules, so traffic handling stayed fail-closed throughout. A defect to schedule, which
is what #240 filed it as.

How

Refuse on a property of the value, not on the presence of the name. load_policy runs a third
guard after the name rule, admitted_only_by_an_unknown_version: when version is the only
recognised key a mapping declares, it admits the document only as the integer POLICY_VERSION (1),
the policy schema version this build reads. Any other value — compose's 2 and 3, the 0 an
absent version defaults to, or a quoted/fractional spelling — is refused for the path in the same
content-free words the other two guards use.

Why that shape and not the obvious one:

  • Dropping version from the name rule is not a fix. It refuses a file containing only
    version: 1, a policy the gateway starts on today, and refusing a policy the gateway runs costs
    an operator an outage rather than a diagnosis. a_policy_declaring_only_its_version_still_loads
    pins that on the binary, through validate and through the gateway; it fails under that fix
    (verified by making the change and watching it fail).
  • The value rule answers only when version is the sole ticket. Beside egress, endpoints
    or rules the value is not consulted, so forward-compatibility of fields is exactly as core: a mapping-shaped secrets file loads as a valid 0-rule policy and is served at GET /api/policy #220
    left it (a_policy_carrying_an_unknown_field_still_loads, with version: 2 beside egress,
    still runs green through the binary), and a mistyped version: "1.0" above rules: still
    reaches serde's own quoting of those three characters with a line and column — the documented
    bound, unchanged. Only a file whose sole recognised key is a mistyped version gets the
    content-free refusal instead.
  • Policy is untouched. No deny_unknown_fields, no field change, no new dependency, no
    decide() change — so this stays off the crates/AGENTS.md Ask first list and out of
    TD-001's TS-type / JSON Schema sync. POLICY_VERSION lives in the CLI beside POLICY_FIELDS
    for the same reason that list does: honmoon-core declares no such constant (the field is a
    plain u32, the schema says only >= 1), and only_this_builds_policy_version_is_an_admission_ticket
    reads the shipped policies/agent.yaml and requires its version to equal the constant, so a
    schema bump that lands in the example fails a test rather than refusing the bumped file at a deploy.

What moves, stated

One file class besides compose loaded before and is refused now: a mapping that declares a schema
version this build does not implement and nothing this build reads at the top level (version: 2
with only unknown siblings). It loaded as deny-all with no rules — a policy this build could not run
as written, #220's class — and the refusal names the version rule, so a rollback that lands there is
a diagnosis. an_unknown_sibling_of_a_recognised_key_still_loads keeps its version: 2\ntelemetry:
fixture: it is a statement about the name rule, which is unchanged and its doc comment now says so.

The remaining bound is a foreign file that opens with an unquoted version: 1 and declares nothing
else honmoon reads. It is admitted, and nothing about that line can tell it from the minimal policy;
the wiki states it where the residual used to be stated.

Tests

Red first, verified rather than assumed:

  • no_command_accepts_a_compose_file_admitted_only_by_its_version (binary, all three commands, on the
    compose file and on a quoted version: "1.0" with nothing beside it) and
    a_compose_file_is_refused_and_a_version_one_policy_is_not /
    only_this_builds_policy_version_is_an_admission_ticket (unit) fail with the new guard neutered
    in place.
  • a_policy_declaring_only_its_version_still_loads fails under the rejected fix (version dropped
    from POLICY_FIELDS).
  • version_alone_admits_a_file_no_operator_wrote_as_a_policy, which pinned the residual, is
    deleted — its own doc comment asked for exactly that once the ticket was narrowed deliberately.
    No other existing test was edited or weakened; no_command_takes_it gained a says argument so
    the compose case can assert which rule answered.

Docs

wiki/getting-started/policy-authoring.md — the residual paragraph becomes the rule, the
"two things" the read says becomes three, the version: "1.0" bound is qualified to the case it
still holds for, and the main.rs anchors are re-resolved against this head. wiki/llms-full.txt
regenerated (scripts/check-wiki-bundle-current.ts holds it byte-identical). The security
reviewer's memory note that recorded the compose case as tracked-in-#240 now records it as closed.

Gates

cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings,
HONMOON_REQUIRE_ENFORCEMENT=1 cargo test --workspace, bun run lint && bun run typecheck && bun test — all green locally.

Closes #240

@codecov

codecov Bot commented Sep 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

The PR appears safe to merge; no actionable correctness, security, or repository-rule violations remain.

Summary

The PR tightens CLI policy-file admission so that a mapping admitted solely by version must declare the policy version supported by this build.

  • Refuses compose-style and otherwise unsupported version-only mappings without exposing their contents.
  • Preserves loading of minimal version: 1 policies and forward-compatible policies containing another recognized field.
  • Adds unit and command-level regression coverage for validation, gateway, and run paths.
  • Updates policy-authoring documentation, the generated wiki bundle, and security-review records.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Read policy file] --> B{Top level is a policy-shaped document?}
    B -- No --> X[Content-free refusal]
    B -- Yes --> C{Mapping names a recognized policy field?}
    C -- No --> X
    C -- Yes --> D{Is version the sole recognized field?}
    D -- No --> E[Deserialize as Policy]
    D -- Yes --> F{version equals POLICY_VERSION?}
    F -- No --> X
    F -- Yes --> E
Loading

Reviews (3) · Last reviewed commit: "test(cli): tie POLICY_VERSION to the shi..."

Comment thread crates/honmoon-cli/src/main.rs Outdated

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses issue #240 by introducing a new validation guard, admitted_only_by_an_unknown_version, which ensures that files containing a version key with an unsupported version number (such as docker-compose files) are rejected when version is the only recognized policy field declared. This prevents foreign configurations from being loaded as valid empty policies. The changes also include updated tests and documentation. The review feedback correctly identifies that several cited line ranges in the documentation should be updated to start at the first line of the respective function's doc comments to preserve context.

Comment thread wiki/getting-started/policy-authoring.md Outdated
Comment thread wiki/getting-started/policy-authoring.md Outdated
Comment thread wiki/getting-started/policy-authoring.md Outdated
Comment thread wiki/getting-started/policy-authoring.md Outdated
@codspeed

codspeed Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 18 untouched benchmarks


Comparing amondnet/issue-240-version-only-admission (2b1db08) with main (57cc747)

Open in CodSpeed

amondnet added a commit that referenced this pull request Sep 19, 2026
…e the forward-compat test doc

Review round on #283. The quoted spelling with nothing beside it was
asserted only of the rule and the loader separately; it now goes through
all three commands, so the order of the guards in load_policy is what the
test sees. The forward-compat unit test's doc says which rule its sentence
is about, since the read as a whole now refuses its first fixture on the
version value. A tag on the version value joins the admitted fixtures,
measured against the loader. Wiki anchors start on each item's doc
comment, and the residual names a concrete file class.
amondnet added a commit that referenced this pull request Sep 19, 2026
…an a second literal

Greptile on #283: asserting the constant against another 1 detects no
drift. The test now parses policies/agent.yaml — the artifact a new policy
is copied from — and requires its version to be POLICY_VERSION, so a
schema bump that lands in the example fails here until the constant
follows. The wiki anchor for the value rule moves with the const doc.
@amondnet

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses issue #240 by introducing a third validation guard in load_policy (admitted_only_by_an_unknown_version). This guard ensures that if a YAML mapping's only recognized policy key is version, it is only admitted if the version value matches the supported POLICY_VERSION (1). This prevents files like docker-compose.yml (which often start with version: 3) from being incorrectly loaded as valid empty policies, while still allowing minimal policies containing only version: 1 to load. The PR also includes comprehensive unit and integration tests to verify this behavior, updates the rejected findings log, and updates the documentation in the wiki and agent memory. There are no review comments provided, so I have no feedback to evaluate.

@amondnet

amondnet commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor Author

CodSpeed record for e33a0f3 (kept here because a rerun rewrites the report comment and the check-run in place — see #127).

Verdict as posted at 02:45:55Z: "Merging this PR will degrade performance by 36.59%", 2 regressed, 16 untouched, comparing e33a0f3 with main (57cc747):

Benchmark BASE HEAD Δ
parse_k8s_path[/api/v1/namespaces/prod/secrets/db-password] 11 µs 18.7 µs -41.29%
parse_k8s_path[/apis/apps/v1/namespaces/staging/deployments/api] 11.8 µs 17.2 µs -31.51%

The third parse_k8s_path argument sat still. This PR changes only crates/honmoon-cli (the policy read path) plus wiki text; parse_k8s_path lives in crates/honmoon-core/benches/policy_engine.rs and nothing this diff touches runs on that path. It is the same benchmark, same one-or-two-of-three-args pattern, and the same BASE drift (5.9–16.4 µs on unchanged code) recorded in #127, where a bare rerun on an unchanged tree flipped the verdict on #142. Treated as non-blocking on that record; the Run benchmarks check-run on this head concluded success.


Follow-up, 02:49:38Z. After a rebase onto 57cc747 (one wiki commit, no Rust change — the crates/ diff is byte-identical), the report for 2b1db08 reads: "Merging this PR will not alter performance", ✅ 18 untouched benchmarks, comparing 2b1db08 with main (57cc747). Same benchmark set, same base, identical crate diff, opposite verdict — the pattern #127 records.

…on this build reads

`load_policy` admitted any mapping that named `version`, and `version` is the
one recognised key other formats use: an unquoted `version: 3` at the top of a
docker-compose.yml loaded as a 0-rule policy whose source `gateway --config`
served at `GET /api/policy`. Dropping `version` from the name rule would refuse
a file containing only `version: 1`, a policy the gateway starts on, so the
read now refuses on the value instead: when `version` is the only recognised
key, it admits the document only as `POLICY_VERSION` (1). Beside `egress`,
`endpoints` or `rules` the value is not consulted, so forward-compatibility of
fields and serde's own diagnosis of a mistyped value are unchanged.

Pinned on the binary in both directions: the compose file is refused on all
three commands with nothing from the file in the message, and `version: 1`
alone still validates and starts the gateway. The wiki paragraph that recorded
the residual now records the rule, and llms-full.txt is regenerated.

Refs #240
…e the forward-compat test doc

Review round on #283. The quoted spelling with nothing beside it was
asserted only of the rule and the loader separately; it now goes through
all three commands, so the order of the guards in load_policy is what the
test sees. The forward-compat unit test's doc says which rule its sentence
is about, since the read as a whole now refuses its first fixture on the
version value. A tag on the version value joins the admitted fixtures,
measured against the loader. Wiki anchors start on each item's doc
comment, and the residual names a concrete file class.
…an a second literal

Greptile on #283: asserting the constant against another 1 detects no
drift. The test now parses policies/agent.yaml — the artifact a new policy
is copied from — and requires its version to be POLICY_VERSION, so a
schema bump that lands in the example fails here until the constant
follows. The wiki anchor for the value rule moves with the const doc.
@amondnet
amondnet force-pushed the amondnet/issue-240-version-only-admission branch from e33a0f3 to 2b1db08 Compare September 19, 2026 02:47
@sonarqubecloud

Copy link
Copy Markdown

@amondnet
amondnet merged commit e722aca into main Sep 19, 2026
13 checks passed
@amondnet
amondnet deleted the amondnet/issue-240-version-only-admission branch September 19, 2026 02:50

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2b1db08978

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/honmoon-cli/src/main.rs
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.

cli: version alone admits a non-policy mapping, so a compose file still loads as a 0-rule policy

1 participant