Orchestrata Ultimate › Plans › briefs › p6b-promoter-verify-settle-hold-builder
P6b — the promoter's verify settles before it judges: builder brief (orchestrator's picks, 2026-10-07)
Source: plans/briefs/p6b-promoter-verify-settle-hold-builder.md at 8a2cb42ad · last changed 2026-10-07 · first committed 2026-10-07 · 236 lines.
Prose pending.
Plan text
P6b — the promoter's verify settles before it judges: builder brief (orchestrator's picks, 2026-10-07)
DESIGN_WAIVER: plan item P6b of plans/ast-daemon-deployment-plan.md § "P6 — enable the
primary instances and prove identity end to end" (bullet "P6b — promoter verify settles before it
judges"): a correctness fix inside the promote's existing verify stage, no new structure and no
new decision; the stage's purpose (ADR-0062: verify refuses false success, or the caller rolls
back) is unchanged and every rule below is stated in the plan. A fork not settled here is a
STOP-and-report.
GOAL: promotion.stages_verify.verify must refuse a unit that is crash-looping at the moment it
polls active. Measured (plan § 3b, row 1fed577b4): orchestrata-conversation-consumer.service
starts, exits 1 after ~1 s on the ADR-0072 RuntimeError, is restarted by systemd every
RestartSec=10, and the promote's verify caught it inside one of its 1-second active
windows and passed. Today the same unit reads ActiveState=failed NRestarts=5 (start limit hit).
1. Exact source at f21ab3af8
Read with .venv/bin/python scripts/serialize_codebase_ast.py source <qname> --no-daemon
(qualified names omit orchestrata_ultimate.; module residue with --residue).
promotion.stages_verify.verify(stages_verify.py:23-153), signatureverify(ctx: DeployContext, *, execute: bool = True, settle: float = 90.0) -> StageResult. Structure:units = ctx.units or daemon_units(ctx.target_root); dry-run branch; inner_inactive()runs[*_systemctl(_scope(ctx.target_root / "deploy" / "systemd" / unit)), "is-active", unit]per unit and keeps those whose stdout is notactive;portsfromCXP_GRPC_HOST/PORT(+CXP_WS_URIwhen declared); the poll loopdeadline = time.monotonic() + settle; while True: inactive = _inactive(); shut = [...]; if not inactive and not shut: break; if time.monotonic() >= deadline: break; time.sleep(1.0); thenproblems("not active: …", "not listening: …") → FAIL; then the zero-units FAIL; thencheck_identity(ctx.target_root, expect_sha=ctx.sha, pattern=ctx.identity_pattern or None); OK detailf"{len(units)} unit(s) active; {len(ports)} endpoint(s) listening; identity OK {identity.sha[:12]} ({identity.proven} unit(s))".- Imports in that module (
--residue):time;check_identity;StageResult, StageStatus;DeployContext;_run, _timedfrompromotion.stages_exec;_declared, _endpoint, _tcp_openfrompromotion.stages_resolve;_scope, _systemctl, daemon_unitsfrompromotion.stages_units. promotion.stages_units._scope(unit_file: Path) -> str(stages_units.py:129-147) and_systemctl(scope: str) -> list[str](:150-158:["sudo","-n","systemctl"]for system,["systemctl","--user"]for user).promotion.stages_exec._run(subprocess wrapper used above,timeout=kwarg) and_timed.promotion.identity_scope.UNIT_PROPERTIES(identity_scope.py:49-…) already lists"ActiveState"and"NRestarts"; it does NOT listRestartUSec. Leave that tuple alone (it is the identity verifier's property set); the verify stage reads its own three properties.- Live format of the three properties (this host, 2026-10-07 07:05):
systemctl --user show -p RestartUSec -p NRestarts -p ActiveState orchestrata-conversation-consumer.serviceprints exactly
andRestartUSec=10s NRestarts=5 ActiveState=failedorchestrata-kernel.serviceprintsRestartUSec=5s. systemd rendersRestartUSecas a timespan:0,100ms,10s,1min 30s,1h 2min 3.5s,2usare all possible renderings. tests/promotion/test_stages_coverage.py:_ctx(repo, target, workspace, *, profile=None, units=None, sha="")(:58-75) builds aDeployContext(env="test", …);_real_venv(root)(:697);_persist_and_start_unit(name, *, working_directory, root) -> Path(:705-735) writes~/.config/systemd/user/<name>with[Service] Type=simple WorkingDirectory=… ExecStart=<root>/.venv/bin/python <root>/_sleep_<name>.py(the script sleeps 30 s),daemon-reload,restart;_reclaim_unit(unit_file, name)(:738-744); classTestVerifyCallsIdentity(:747-898) with_root(tmp_path) -> (root, sha)(real git init,.gitkeep, commit,_real_venv) andtest_a_real_matching_deployment_passes_identity(:825-850): writesVERSIONasf"commit {sha}\nref origin/master\nrepo x\ndeployed {datetime.now(UTC).replace(microsecond=0).isoformat()}\n", starts a real unit, binds a real listener for the kernel port,ctx.identity_pattern = f"^{name}$",verify(ctx, settle=10.0)→StageStatus.OK,"identity OK" in result.detail.tests/promotion/test_stages_coverage.py::TestVerifyPollAndDeadline(:407-469): the dry-run, zero-units and deadline tests;test_inactive_units_and_shut_ports_fail_at_the_deadlineusessettle=0.5and asserts the poll ran to its deadline.tests/promotion/test_promotion_prod_lessons.py::TestWhatTheFirstProdDeploymentTaught:test_verify_waits_for_a_port_rather_than_sleeping_a_guess(:86-139, zero units, late-bound port,settle=15.0, expects FAIL "no units to verify") andtest_the_settle_default_lives_only_in_verify(:204-212:verify'ssettledefault must stay90.0;deploy's must stayNone).tests/promotion/_identity_fixtures._transient_unit(name, *, working_directory, exec_start, scope="user", type_="simple", on_active=None, extra_properties=(), expect_success=True)(_identity_fixtures.py:153-216): a REALsystemd-runtransient unit,-pproperties fromextra_properties, stopped +reset-failedon exit. Prefer_persist_and_start_unit's persisted-file pattern for the crash-loop unit (a persisted unit file can carryRestart=always,RestartSec=2,StartLimitIntervalSec=0;systemd-runcan too via-p, either is acceptable — pick one and say which).
2. The change (plan P6b, three rules)
Rule 1 — a timespan parser. Add to promotion/stages_units.py:
parse_systemd_timespan(text: str) -> float returning seconds. Accepts the forms systemd
prints: an optional sign-free decimal number followed by one of the unit suffixes
y (31557600 s), month (2629800 s), w, d, h, min, m (minutes, as systemd), s,
ms, us, µs, ns, with any number of whitespace-separated terms ("1min 30s" → 90.0);
bare "0" → 0.0; "infinity" → float("inf"); anything else (empty string, "abc",
"10 s s", a bare number other than 0) raises ValueError naming the text. No regex from the
stdlib beyond re; no new dependency.
Rule 2 — the settle hold. In verify, after the poll loop breaks with not inactive and not shut
and BEFORE the problems block, add a hold over the non-empty units:
- For every unit read
RestartUSec,NRestarts,ActiveStatewith ONE call per unit:_run([*_systemctl(_scope(unit_file)), "show", "-p", "RestartUSec", "-p", "NRestarts", "-p", "ActiveState", unit], timeout=30), parsed askey=valuelines (a helper_unit_properties(ctx, unit) -> dict[str, str]). A unit whoseshowreturns non-zero or lacks any of the three keys is a FAIL"settle hold: cannot read RestartUSec/NRestarts/ActiveState for <unit>". hold = max(parse_systemd_timespan(p["RestartUSec"]) for every unit) + 1.0; if the maximum isinf, use1.0and say so in the OK detail (an un-restartable unit cannot crash-loop).time.sleep(hold)once (not per unit).- Re-read the three properties for every unit. Refuse with
"after the settle hold (<hold:.1f>s): <unit> restarted <N> time(s)"for any unit whoseNRestartsis not"0", and"after the settle hold (<hold:.1f>s): not active: <unit> (<ActiveState>)"for any whoseActiveStateis notactive; join all such lines with"; "into the FAIL detail. Order of refusals: restarted first, then not-active, units inunitsorder. - On success, the OK detail gains
"; settled {hold:.1f}s (NRestarts 0)"after the existing"identity OK …"text. The zero-units FAIL and the identity check stay exactly where they are and run after the hold.
The hold is NOT bounded by settle (that is the poll's patience, measured on the kernel's 9-s
bind); keep settle's default 90.0 and deploy's None. Do not add a constant default for
the hold: the plan's rule is "read from the unit properties, not a constant".
Rule 3 — the proof tests. New module tests/promotion/test_verify_settle_hold.py (RUL-021 file
budget: do not grow test_stages_coverage.py), real units only, no doubles, no monkeypatch,
no skip, test_id fixture for unit names (orchestrata-p6b{test_id.lower()}….service):
test_timespan_parsing_matches_systemd_renderings—pytest.mark.parametrizeover("0", 0.0), ("100ms", 0.1), ("10s", 10.0), ("1min 30s", 90.0), ("2min", 120.0), ("1h", 3600.0), ("1h 2min 3.5s", 3723.5), ("2us", 2e-6), ("infinity", inf); andtest_timespan_parsing_refuses_garbageover("", "abc", "10 s s", "7", "10 sec")→ValueError.test_a_crash_looping_unit_is_refused_after_the_hold— a persisted real user unit whose ExecStart is/bin/sh -c 'sleep 2; exit 1'withRestart=always,RestartSec=1,StartLimitIntervalSec=0,WorkingDirectory=<root>; root + VERSION + listener +identity_patternexactly astest_a_real_matching_deployment_passes_identity;verify(ctx, settle=20.0); assertresult.status is StageStatus.FAIL,"after the settle hold"inresult.detail, the unit name inresult.detail, and that"identity"is NOT inresult.detail(the hold refuses before identity runs). Reclaim the unit infinally.test_a_steady_unit_passes_and_the_hold_is_read_from_the_unit— the same scaffold with the sleeping unit from_persist_and_start_unitbut a unit file carryingRestartSec=3(Restart=on-failure); measureelapsedaroundverify(ctx, settle=10.0); assert OK,"settled 4.0s" in result.detail,elapsed >= 4.0.test_a_unit_that_cannot_be_shown_is_refused—units=["orchestrata-p6b{test_id}-ghost.service"]that was never loaded, a real listener on the kernel port: the poll already fails onis-active(notactive), so this test asserts the EXISTING"not active:"refusal and documents that the hold is only reached once every unit is active. (Keeps the branch order honest; no new behaviour claimed.)- Existing tests must stay green unchanged:
TestVerifyCallsIdentity(defaultRestartUSecrenders100ms→ hold 1.1 s),TestVerifyPollAndDeadline, the twotest_promotion_prod_lessonsverify tests.
@traces: the new function and the hold carry the plan's requirement citation the way the
rest of promotion/ does — check promotion.stages_verify and promotion.identity for the
exact decorator/comment form used (check_spec_authority.py must stay rc 0; if no requirement
id exists for the promote's verify stage, cite none and say so in the report rather than
inventing one).
Docs: docs/ship-and-deploy.md — in the promote section, one sentence after the verify
description: the verify stage holds for the longest RestartSec + 1 s after the poll succeeds and
refuses any restarted unit (NRestarts), so a crash-looping unit cannot pass on a lucky sample.
3. Gates and done-when
- Pytest only as
OPENBLAS_NUM_THREADS=1 OMP_NUM_THREADS=1 MKL_NUM_THREADS=1 PYTHONPATH=scripts timeout 300 .venv/bin/python -m pytest <modules> -p orchestrata_pytest_observer -p orchestrata_ultimate.core.threading.pytest_live_observer -q -p no:cacheprovider -rs, background + log in/tmp/claude-1001/-home-mohamed/42ea036c-e296-4b26-b447-2f612e9dd49a/scratchpad/p6b_<name>.log+echo "rc=$?" >> <log>, waited withtimeout 290 bash -c 'until grep -q "^rc=" <log>; do sleep 10; done'. Modules:tests/promotion/test_verify_settle_hold.py,tests/promotion/test_stages_coverage.py,tests/promotion/test_promotion_prod_lessons.py,tests/promotion/test_promotion_units.py,tests/promotion/test_runner_coverage.py,tests/scripts/test_promote_env*.py. Zero skips. - Coverage ≥ 90% on
stages_verify.pyandstages_units.py(report the numbers from--cov=orchestrata_ultimate.promotion --cov-report=term-missing). uv run ruff check,uv run black --checkon changed.py;uv run pyright <changed .py>zero new errors;uv run python scripts/check_spec_authority.pyrc 0.- Litter: restore
docs/CODEBASE_MAP.md docs/CODEBASE_MAP_FULL.md plans/ast-ultimate.json plans/ast-deep-comparison.json plans/ast-tests.json reports/.ship-verified reports/run-tests-latest.jsonby name before committing; never baregit stash. - Commits as
git -c user.name=Mohamed -c user.email=msamy@sufigeeks.org commit, on top of f21ab3af8, no AI attribution, no "should" in the diff (the word is forbidden everywhere), no->in the commit message (it trips the redirect hook). - Do NOT run
promote_env.py promote,deploy/ship.sh, or restart anyorchestrata-*unit on this host; the orchestrator ships. The only real units you create are your ownorchestrata-p6b*test units, reclaimed infinally. - Report: shas, per-module counts with log paths, coverage numbers, the exact FAIL detail string the crash-loop test observed, deviations. A fork not settled above is a STOP-and-report.
4. Fix round 1 (reviewer conditional GO on 7ac5e7f79, 2026-10-07; commits on top of 7ac5e7f79, no rebase or amend)
Reviewer probes (scratchpad p6b_rev_q1.log, p6b_rev_f1.log, p6b_rev_slow.log,
p6b_rev_probe.py): with sleep 1; exit 1, RestartSec=10, NRestarts rises in the same
0.2-s sample as the switch back to active (0.01–0.92 s 0 active; 1.15–10.79 s
0 activating; 11.02 s 1 active), so the real verify returned
FAIL after the settle hold (11.0s): <unit> restarted 1 time(s) in 11.0 s: the incident shape
is refused by the NRestarts branch, every time. Every RestartUSec rendering probed (0,
100ms, 1s, 1.500000s, 1min 30s, 2h, infinity, 1d 2h, 1us, 59min, 1w,
1d 1h, 1y 2month) parses. daemon_units(/opt/orchestrata) returns only the four
long-running services (oneshots have sibling timers and are excluded), so the hold never
judges a oneshot. The hold is read from the unit (the 4.0-s and 1.0-s assertions prove it).
MAJOR-1 — the restarted N time(s) branch has no test that reliably reaches it.
test_a_crash_looping_unit_is_refused_after_the_hold (sleep 2; exit 1, RestartSec=1,
hold 2 s) re-reads ~0.05 s after the exit, inside activating with NRestarts=0, so only the
not active branch fires and the assertion ("after the settle hold") cannot tell the
branches apart; dropping the NRestarts check keeps the suite green. Fix: (a) rename that
test test_a_unit_crashing_inside_the_hold_is_refused_as_not_active and give it a 1-s margin
on each side: ExecStart=/bin/sh -c 'sleep 2.5; exit 1', Restart=always, RestartSec=5,
StartLimitIntervalSec=0 (hold 6.0 s; exit at 2.5 s, restart fires at 7.5 s; the re-read at
~6 s is activating, NRestarts=0); assert
"after the settle hold (6.0s): not active: <unit> (activating)" in result.detail and
"restarted" not in result.detail. (b) add test_a_crash_looping_unit_is_refused_as_restarted
with the reviewer-measured stable shape (3 of 3 runs): ExecStart=/bin/sh -c 'sleep 0.8; exit 1',
Restart=always, RestartSec=2, StartLimitIntervalSec=0 (hold 3.0 s; the re-read lands in
the second active window, 2.8–3.6 s); assert
"after the settle hold (3.0s): <unit> restarted 1 time(s)" in result.detail,
"not active" not in result.detail, "identity" not in result.detail. Both units reclaimed
in finally.
MAJOR-2 — the texts claim more than the code delivers. A unit whose first crash comes
later than poll time + RestartSec + 1 s passes the hold every time (probes: sleep 15; exit 1
with RestartSec=10 and sleep 3; exit 1 with RestartSec=1 both reached the identity
stage). The guarantee the code gives is bounded: a unit that crashes within the longest
RestartSec + 1 s after the poll finds it active is refused; one that crashes later than that
is not seen by this stage. Fix: correct the _settle_hold docstring (the sentence "surviving
a full restart interval plus a second is what cannot be sampled by luck") and the
docs/ship-and-deploy.md § 5 sentence ("a crash-looping unit cannot pass on a lucky sample")
to state exactly that bounded guarantee and name what it does not cover. No minimum-uptime
floor is added in this round; the plan records it as a P6 follow-up for the orchestrator to
size (it changes how long every promote waits).
MINOR-1 — unit file leak on a failed start. In test_verify_settle_hold.py the
_start_unit(...) call and kernel.bind run before try: in the unit tests; a failing
systemctl restart (check=True) leaves the file under ~/.config/systemd/user/. Fix: create
the unit file and start it inside the try, and make the reclaim helper tolerate a missing
file and an unknown unit (unlink(missing_ok=True); stop/reset-failed without check).
MINOR-2 — the hold has no upper bound. RestartSec=1d 2h renders 1d 2h and the stage
would sleep 93601 s. Fix: if longest + 1.0 > settle, refuse WITHOUT sleeping:
"settle hold: <unit> RestartUSec <rendered> exceeds the verify patience (<settle:.0f>s)",
naming the unit with the longest value. settle is the stage's existing patience parameter;
no new constant. Proof test
test_a_restart_interval_longer_than_the_patience_is_refused_without_waiting: a steady unit
with RestartSec=200, verify(ctx, settle=10.0), assert FAIL with that string and
elapsed < 5.0.
Gates and commit rules as § 3. Modules to run: tests/promotion/test_verify_settle_hold.py,
tests/promotion/test_stages_coverage.py, tests/promotion/test_promotion_prod_lessons.py,
tests/promotion/test_promotion_units.py. Report the exact detail strings the two crash tests
observed and the elapsed of the patience test.