Skip to content

test(protocols): SMB ACL scenario accepts the host gid in either Samba rendering - #47

Merged
Amantux merged 1 commit into
masterfrom
fix/smb-acl-test-gid
Oct 11, 2026
Merged

Amantux merged 1 commit into
masterfrom
fix/smb-acl-test-gid

Conversation

@Amantux

@Amantux Amantux commented Oct 11, 2026

Copy link
Copy Markdown
Owner

First-ever full-suite run on master (dispatched manually: https://github.com/Amantux/OpenBlade/actions/runs/38114428970) was 3217 passed / 1 failed. The one failure, tests/e2e/protocols/test_smb.py::test_smb_acl_maps_posix_mode, hard-coded Unix Group\root; on the ubuntu runner the host gid is 1001 and Samba renders it numerically. The assertion now derives the expected group from the file's real gid and accepts either rendering. Verified on the local rig (33/33) and mutation-checked.

Noticed while chasing this: the auto-merge workflow merges with GITHUB_TOKEN, so the push-to-master full-suite job never fires for auto-merged PRs (no CI run existed for 069694c). Not changed here — needs a token decision.

🤖 Generated with Claude Code

https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

…a rendering

mode640.txt is written host-side by rig.py, so its group is whatever gid runs
the suite. Samba renders a gid it can resolve by name ("Unix Group\\root"
locally) and an unresolvable one numerically ("Unix Group\\1001" on a
GitHub-hosted runner). The assertion hard-coded root and failed on the first
ever full-suite run on master; it now derives the expected group from the
file's real gid and accepts both forms. Mutation-checked: a wrong gid fails.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD
@Amantux
Amantux merged commit e41a7a1 into master Oct 11, 2026
14 checks passed
@Amantux
Amantux deleted the fix/smb-acl-test-gid branch October 11, 2026 05:41
github-actions Bot pushed a commit that referenced this pull request Oct 11, 2026
…s, appliance preflight, reboot setting (#48)

* ci(auto-merge): wait for every required check and dispatch master lanes after merge

Trigger on workflow_run of both CI and operability. Before merging, read the
required contexts from branch protection (fallback ["ci-gate",
"operability-gate"] on 403/404, which GITHUB_TOKEN normally gets) and merge
only when every one is "success" on the PR head SHA (check runs, newest per
name, plus combined commit statuses). Pending -> notice and exit; the other
workflow's completion is the retry (PR #47 merged-while-in-progress failure).
Failed -> notice and exit. The merge pins sha=head to avoid racing a push.

After a successful merge, dispatch ci.yml and operability.yml on master: a
GITHUB_TOKEN merge emits no push run, so full-suite never ran on the merge
commit (PR #46). Job grants checks:read, statuses:read, actions:write
per-job; workflow-level stays contents:read.

decide() is a pure function with three worked examples in comments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* docs(readme): document trusted auto-merge two-workflow wait and post-merge dispatch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* fix(safety): consume format safety token atomically before the job

delete_safety_token now runs DELETE under BEGIN IMMEDIATE and returns
whether this caller consumed the token; FormatService.confirm consumes
first and refuses on False, so concurrent confirms with one token run
the job once and a failed job does not re-arm the token.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* fix(auth): fail closed at startup in production on open auth gates

Production refuses to start (typed ConfigurationError, names vars, never
values) when OPENBLADE_API_TOKEN/_FILE is unset, CORS origins contain '*',
or the AML admin/service password or service token is the default.
Elsewhere one multi-line banner lists every open gate. validate_config
reports the same set, so scripts/validate_config.py stays the gate.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* test(hardware): opt-in appliance soft-reboot persistence test

Reboots via POST /aml/system/reboot only when OPENBLADE_REBOOT_TESTS=enabled
on a real-hardware run; otherwise skips. Refuses to reboot unless the library
is quiescent (no live leases, no drive loaded or mounted). A dropped
connection after the POST counts as accepted; login_ok treats
HTTPException as down; polls every 2s within bounded 300s/600s waits.
Does not call recover_after_restart (no report-only mode); the lane's
recovery-report step is the recovery assertion.

A no-hardware unit test pins that RebootClient.request keeps the read-only
guard (refuses POST /aml/media/move and POST /aml/system/reboot); mutation-
checked: widening request makes it fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* ci(hardware-destructive): opt-in reboot persistence step

Runs tests/hardware/test_reboot_persistence.py only when repo var
HARDWARE_REBOOT_TESTS == 'true' and every earlier step succeeded
(success(), never always()), exporting OPENBLADE_REBOOT_TESTS=enabled.
Placed after the recovery report and the lane's after-snapshot (so the lane
diff is not polluted by the reboot) and before the snapshot upload; same
environment approval. timeout-minutes: 20; TLS verification pinned on
(OPENBLADE_APPLIANCE_INSECURE=false); -p no:cacheprovider --tb=short.
An unset var yields a visible ::notice:: instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* feat(hardware): add read-only appliance preflight tool

python -m tools.hardware.preflight --lane readonly|destructive [--json]:
env, tools, device/inquiry/serial/by-id/serial-map, mtx status drive count,
SACRIFICIAL_BARCODES inventory + not-loaded, appliance login/logout,
armed-env warning. Route tools/hardware/* to the unit category.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* test(hardware): unit-test every preflight check with fakes

Mutation-checked: disabling the duplicate-serial and allowlist-loaded
guards each fails its test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* fix(auth): make the test-runner route dev-only and loopback-targeted

POST /api/test-runner/run returns 404 unless OPENBLADE_ENABLE_TEST_RUNNER=true
and OPENBLADE_ENV != production, and rejects any i3_aml_url whose host is not
localhost/127.0.0.1/::1/host.docker.internal (SSRF via spawned pytest env).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* docs(runbooks): document the opt-in reboot persistence setting

§2 gains HARDWARE_REBOOT_TESTS; §5 "Reboot persistence" describes the
two-level opt-in (repo var + OPENBLADE_REBOOT_TESTS), the success()-only
ordering after the lane's after-snapshot, the quiescence precondition, the
bounded waits, the pinned TLS verification, and why the test does not call
recover_after_restart. The hard power-cycle stays a manual fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* ci(hardware): run the preflight tool instead of inline env checks

All three rig lanes call python -m tools.hardware.preflight --json, print
the report on failure and upload preflight.json. The job-level
OPENBLADE_REAL_HARDWARE_ENABLED is blanked for that step so the preflight
never runs armed. HARDWARE_RIG_AVAILABLE gating is untouched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* ci(auto-merge): address review — keep retries, Actions-only checks, fail on dispatch error

- Drop the job-level conclusion=='success' filter: decide() already enforces
  success per required check, and skipping non-success completions could strand
  a PR that was waiting on that workflow.
- Count only check runs whose app is github-actions, so a look-alike gate
  name from another app cannot mask a real failure.
- core.setFailed when a post-merge dispatch fails, so a missing full-suite
  run on master is visible instead of a warning on a green job.
- Per-head-SHA concurrency group (no cancel) so near-simultaneous CI and
  operability completions do not race two merges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* docs(readme): state the auto-merge fallback list and master-tip dispatch accurately

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* build: add make hw-preflight target

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* docs(runbook): add §0 Day one checklist and reference the preflight

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* feat(deploy): bind compose to loopback; add opt-in production LAN override

docker-compose.yml publishes 8000/8010/5173/5175 on 127.0.0.1 only.
deploy/lan/docker-compose.lan.yml (make lan-up/lan-down) publishes api/web/
web-flask on ${OPENBLADE_BIND_IP:-0.0.0.0}:8090/8091/8092 via ports !override,
OPENBLADE_ENV=production, and requires every secret via ${VAR:?}.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* ci: only PR runs cancel in-progress runs; dispatched master runs never cancel each other

Auto-merge now dispatches CI and operability on master after every merge. With
cancel-in-progress unconditional, two quick merges cancelled the first merge's
full-suite, leaving a commit on master with no post-merge result.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* feat(auth): warn on insecure Flask cookies in production; document LAN exposure

validate_config warns (insecure_session_cookies) when production sets
OPENBLADE_WEB_SECURE_COOKIES off. Tests pin the existing Flask refusal
without FLASK_SECRET_KEY and Secure-by-default cookies in production.
docs/deployment.md gains 'Exposing on a LAN'.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* fix(nas): refuse ingest start when pool_id differs from the plan's booked pool

start_ingest booked capacity against plan.pool but created the dataset in
request.pool_id, so a mismatch wrote to an unbudgeted pool and a pool-less
preview could start into any pool with nothing booked. Now 409 on mismatch
and the job is created in plan.pool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* feat(safety): enforce scratch-media allowlist in FormatService on real backend

Barcode-list parsing moves to openblade/domain/media_allowlist.py (the CI
tool now imports it). On a REAL backend, dry_run and confirm refuse any
barcode not in OPENBLADE_SCRATCH_BARCODES; an empty allowlist refuses
everything. Simulator behaviour is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* fix(nas): book share-push capacity before registering the plan

A refused booking (409) used to leave the plan orphaned in the in-memory
registry. Book first, register after, and drop the plan via the new
unregister_archive_plan if job creation fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* feat(safety): real backend requires an API token; accept simulator alias

load_config raises RealHardwareDisabledError when OPENBLADE_BACKEND=real
and OPENBLADE_REAL_HARDWARE_ENABLED=true but no OPENBLADE_API_TOKEN(_FILE)
is set, and validate_config reports it as BLOCKING. OPENBLADE_BACKEND=
simulator (the Dockerfile value) now maps to mock explicitly, and any
other unknown value logs a warning instead of silently mocking.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* docs(safety): document new gates; flag vacuous operator_acknowledgment

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* fix(nas): roll back the capacity booking when an ingest start fails

Both start paths (start_ingest and share-push) now release the plan's
booking and unregister the plan if anything after book_plan_capacity raises,
then re-raise the original error. release_plan_capacity logs and swallows a
failed release so it can never mask the start failure (booking expires by TTL).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* docs(decisions): mark the capacity start-path risks resolved

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* ci(hardware): mint a masked per-run API token in every real lane

Real mode now refuses to start without OPENBLADE_API_TOKEN. Each rig lane
mints one per run (masked) before preflight/pytest; mhvtl threads it through
the explicit sudo env lists and env.sh emits it (keeping the caller's).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* refactor(hardware): share the read-only ApplianceClient between the lane and preflight

Move ApplianceClient, ApplianceResponse, ReadOnlyViolation and the request
guard into openblade/hardware/appliance_client.py. The read-only conftest
re-exports them (fixtures and RebootClient unchanged); preflight's duplicate
_UrllibAppliance becomes a thin probe over the shared client, keeping its
scheme check and curated unreachable error. Every preflight request now also
passes the read-only guard.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* fix(config): redact credentials from OPENBLADE_DB_URL in validation findings

The startup banner logs WARNING findings verbatim; a postgres URL with a
password leaked into logs. Add redact_url() (scheme+host+path, creds ***).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* fix(nas): ingest refuses to auto-format on a real backend

_ensure_formatted minted its own SafetyToken and called ltfs.format, so on
OPENBLADE_BACKEND=real ingest could mkltfs any unformatted tape, bypassing
the scratch allowlist and the persisted single-use token. On a real backend
(same detection FormatService uses, now real_backend_from_config) it now
raises FormatRequiresConfirmationError naming the barcode and the two-phase
/ltfs/format flow; run() marks the job FAILED and nothing is written.
Simulator keeps the direct auto-format.

Mutation-checked: dropping the real-backend check fails the refusal test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* fix(format): normalise barcodes once at the FormatService boundary

is_scratch_barcode compared strip().upper() but dry_run/confirm used the raw
barcode, so " abc123" passed the allowlist and then failed as unknown.
dry_run and confirm now normalise (strip().upper()) before the allowlist,
inventory lookup, token and catalog. Mutation-checked: an identity
normalize_barcode fails the new test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* fix(hardware): preflight FAILs (exit 1) when run armed

check_armed only WARNed when OPENBLADE_REAL_HARDWARE_ENABLED was set,
although preflight is read-only and must never run armed. It now FAILs,
so main() returns EXIT_FAIL. The three lane preflight steps already blank
the flag (hardware-nightly-readonly.yml:88, hardware-library-smoke.yml:89,
hardware-destructive.yml:102), so lanes are unaffected. Mutation-checked:
reverting to WARN fails both armed tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* docs(agents): ingest never formats on a real backend

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* fix(auth): reject blank, short (<12) and default AML passwords

password_problem() strips whitespace, enforces 12 chars and rejects known
defaults; shared by the production BLOCKING gate, the dev WARNING, and the
aml_state getters (production raise), so a ' ' value no longer passes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* ci(auto-merge): require owner approval on head SHA for non-owner trusted authors

Copilot PR content is steered by issue text; it now merges only when
Amantux's latest substantive review is APPROVED on pr.head.sha (pure
reviewGate(), listReviews). Amantux still merges directly. README updated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* test(format): claim a safety token from two independent repositories once

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* test: armed-real-backend fixtures supply the now-required API token

Real mode refuses to load without OPENBLADE_API_TOKEN(_FILE); the five tests
that arm real hardware through monkeypatched env now set a token, and the
clean-env fixture clears it so default assertions stay meaningful.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* fix(capacity): isolate rollback cleanups so the original start error propagates

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

* ci(operability): positive-control production config carries an API token

Production now refuses to promote with the native API open (native_api_open),
which is exactly what the gate is for; the positive control must therefore set
OPENBLADE_API_TOKEN like any real production deployment.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uyKrkqBY5KxLuSjotaVGD

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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