Skip to content

S1 (v5): read-only unit classifier prototype, systemd/dpkg behaviour proved on ubuntu VMs, pastebin read - #35

Merged
djbclark merged 20 commits into
masterfrom
v5-s1-classifier
Nov 2, 2026
Merged

djbclark merged 20 commits into
masterfrom
v5-s1-classifier

Conversation

@djbclark

@djbclark djbclark commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Slice S1 of docs/architecture/schema-v5-dpkg-reconciliation-2026-11-02.md (§3, §3.5, §6 S1 fixtures; R4-R8).

  • bin/unit_classifier.py: read-only prototype of the §3 detector contract (Python 3 stdlib). Gate (§3.1), enumeration through the manager (§3.2), dpkg ownership with /lib canonicalisation (§3.3), verdicts (§3.4), summary in the §2.3(2) row shape (counts only when complete, R3d). Read-only is enforced in code: Env.run refuses any argv outside systemctl show|cat|list-units|list-unit-files, systemd-analyze unit-paths, dpkg-query -S; filesystem access is lstat/readlink/listing only.
  • tests/unit_classifier/: 42 pure-logic tests (Mac and CI); 57 VM fixtures, one per row of the spec's S1 table plus 5 (root + TENDCF_TEST_SYSTEMD=1); s1_probe.py for §3.5 items 1-9. Wired into linux.yml's vm job.
  • Spec §3.6: the observed behaviour (systemd 255.4/dpkg 1.22.6 on 24.04 amd64+arm64, systemd 259.9/dpkg 1.23.7 on 26.04), the fixture-to-test table, and contract changes K1-K13 marked inline [S2 K] for S2.

CI: all 57 fixtures green on ubuntu-24.04, 24.04-arm and 26.04 (runs 41506950654, 41507052944). Mutation: every unowned alias link authorised -> exactly 3 fixtures red on all three runners (run 41506077208) plus the logic-suite case; restored (the mutant and its revert stay in history, 11b03da/c745e96).

pastebin (R8, the one allowed read-only probe; ran unprivileged, nothing written): extra_scan complete, 249 units: 247 clean, 1 extra (tendcf.canary.service: the claims sketch lacks the canary's service entry, correct for the input), 1 two-writers (systemd-zram-setup@zram0: a generator-written drop-in, new K13). The two index-based attributions are measured: /etc/init.d/exim4 -> exim4-base, /etc/init.d/zhm -> zephyr-clients.

Open for S2 (spec §7 item 13): K5 (generated units with empty SourcePath keep stock 26.04 incomplete) and K13 (generator-written drop-ins): attribute through the generator, or keep the loud verdict.

Author: Claude Sonnet 5.5 high (omp slice); lead: Opus 5.5 high (the pastebin run, §3.6 pastebin results, K relabel).
Reviews (cross-model, read-only, high effort):

  1. astra (gpt-6-astra), on 394a19c: changes requested. P1: the fixture sandbox restore wrote through a replacement symlink (a mask: /dev/null written and chmodded, the unit left masked). P2s: drop-in link targets treated as fragments; unresolved link chains dropped silently; malformed unit-paths output gave complete with zero counts; the probe exited 0 after failed steps. Fixed in bdf2504, each with a regression that fails before; plus a 120 s per-command timeout (K12, new K14).
  2. astra, re-review of bdf2504: the five fixes confirmed; changes requested for four more in the same families (path dedup lost a fragment's role, a false clean; manager-call exceptions skipped the restore; unchecked probe observations; chmod before chown). Fixed in d8b63f6; mutant d6f65bc (chmod before chown) turned exactly the setuid fixture red on all three runners, reverted in 1ae1069.
  3. Fable 5.1 (standing in: astra short, Codex weekly quota 0%, refused at dispatch), re-review of 1ae1069: approve with changes; four fixes confirmed, no new fail-open path. Its P3 (fragment-chain link lost its via, so K10 was order-dependent, over-reporting only) and three P4s (Sandbox.start bypassed the runner; a CleanupError hid the step exception; JSON artifacts lacked via/role) all fixed in 002bf39 with regressions.
    Two earlier astra attempts ended with no report: a sandbox-blocked gh call made codex abort the turn. Harness fix: acp-run: codex --perm read-only never asks (a rejected escalation aborts the turn) djbclark/djbclark-ade#263.

Final CI on 2267cbb (branch merged with master after #36): Linux run 41538693801 success on ubuntu-24.04, 26.04, 24.04-arm and debian-13: 105 logic tests, 75 VM fixtures. Check success.

Note: the pastebin numbers above came from the classifier at 394a19c. Later fixes only add fail-closed paths and role bookkeeping; S4 re-reads pastebin anyway (§6).


Note

Medium Risk
Large new host-facing scan logic and root CI fixtures touch systemd and dpkg on real managers, but the tool is read-only, prototype-only, and heavily tested; policy enforcement is unchanged.

Overview
Adds schema v5 slice S1: a read-only bin/unit_classifier.py prototype that implements the §3 detector contract (gate, manager enumeration, dpkg ownership, §3.4 verdicts, report-row summary). It is not wired into the CFEngine bundle; Env.run whitelists only read-only systemctl, systemd-analyze unit-paths, and dpkg-query -S.

Tests and CI: tests/unit_classifier/ adds canned test_logic, root/systemd test_fixtures (57 rows from the spec table), s1_probe.py for §3.5 behaviour items, plus s1lib sandbox/dpkg helpers and pastebin-claims.json. The linux.yml vm job runs logic tests, installs openssh-server, runs the probe and fixtures under TENDCF_TEST_SYSTEMD=1, and uploads s1-probe-* artifacts—before the step-7 install that takes over /var/cfengine.

Spec: schema-v5-dpkg-reconciliation-2026-11-02.md records that S1 ran on three Ubuntu runners and pastebin; new §3.6 documents observations and inline [S2 K1–K14] contract deltas for the next slice.

Reviewed by Cursor Bugbot for commit 2267cbb. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor

cursor Bot commented Nov 2, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: f3c7aea9-206d-4dfc-8210-27940dfaa166)

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-02T19:22:40.087815Z 394a19c PR opened
🔒 Security Review ✅ Completed 2026-11-02T19:23:11.039738Z 394a19c PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: 394a19c7e8

ℹ️ 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 bin/unit_classifier.py Outdated
Comment on lines +607 to +609
add(Artifact(fp, "dropin", link_target=fst[1]))
if final is not None and not devnull:
add(Artifact(final, "target", name=e))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve drop-in provenance when classifying symlink targets

When a drop-in is a symlink, its final target is recorded as a generic target, which judge() includes in local_frag or vendor_frag. Consequently, a declared vendor unit with an explicitly permitted local drop-in such as x.service.d/override.conf -> /opt/config/override.conf is reported as two-writers by rule 3, even with an exact third-party claim: the target is mistaken for a replacement fragment. I reproduced this with the existing FakeSystem harness. Preserve the originating artifact kind through link resolution so drop-in targets receive the drop-in rules rather than fragment-shadowing rules.

Useful? React with 👍 / 👎.

Comment thread bin/unit_classifier.py
Comment on lines +579 to +580
if final is not None:
add(Artifact(final, "target", name=n, via=canon_path(env, p)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mark unresolved link targets as unverifiable

When link_final() returns None for a dangling link, a cycle, or an exhausted traversal limit, the probe retains the link without recording a problem. For templates, which deliberately bypass show() and cross_check(), this allows a declared package's unresolved template link to produce a clean verdict and a complete scan with zero findings. I reproduced that result with FakeSystem using x@.service -> /opt/config/x@.service with the target absent. This violates the detector contract that unresolved artifact provenance makes the scan incomplete; record an unverifiable-artifact problem instead of silently omitting the target.

Useful? React with 👍 / 👎.

…nup failures fail the run; drop-in link targets keep the layer role (K14); unresolved link chains, malformed unit-paths and listing records fail closed, commands time out (K12); probe exits nonzero on unexpected failure
@cursor

cursor Bot commented Nov 2, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 12ddbb6c-f292-4975-aca9-7b44df0eb100)

…e keeps its fragment role in any scan order; sandbox collects manager exceptions and always restores; probe observations are single checked commands; restore chowns before chmod
@cursor

cursor Bot commented Nov 2, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 0a3f3e27-4040-4805-bd5b-5916040bf664)

@cursor

cursor Bot commented Nov 2, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 97e959a5-570f-4ecd-a1fb-d33244b017e4)

@cursor

cursor Bot commented Nov 2, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 91e19b06-85ba-4a7d-8fcd-a1763f00baa9)

…o it (K10 in any scan order); Sandbox.start goes through the runner and a None runner result is a failure; probe reports the exception a CleanupError hid; JSON artifacts carry role and via
@cursor

cursor Bot commented Nov 2, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: a8d1bfe2-125f-4930-a5e1-413366dafc4a)

@cursor

cursor Bot commented Nov 2, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 25019fc7-0987-4b96-b4ce-2741f10b36fe)

@djbclark
djbclark merged commit 93577a5 into master Nov 2, 2026
7 checks passed
@djbclark
djbclark deleted the v5-s1-classifier branch November 2, 2026 22:50
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.

1 participant