Repository navigation
extdeps.systemd.systemd_run: typed options, one projection, ArgvCommand everywhere - #13257
Conversation
…nd everywhere The seven string-glued builders (systemd_run_property_argv, systemd_run_user_transient_arguments, systemd_run_user_wait_arguments, systemd_run_system_scope_argv, systemd_run_property, systemd_run_transient_unit_argv, systemd_run_transient_wait_unit_argv) are deleted. In their place: - SystemdRunOption, a sealed sum whose arms carry systemd-run(1)'s own spellings at systemd 255 (the major the summary ground reads): RunUnit, RunUserManager, RunScope, PropertyShort, PropertyLong, SetEnv, Wait, Quiet, Pipe, Collect, EndOfOptions. - systemd_run_option_words, ONE projection from options to words, with refusals at the wall: a unit name carrying / or a newline, a property value carrying a newline, a setenv name carrying = (the wire cannot carry them; they are refused, not trimmed or split). An unknown flag has no arm to ride: the sum is closed and the projection's fold walks every variant. - Every builder returns ArgvCommand through SystemdRunCommandReading (Ready | Refused); extdeps.exec.command's admit list now admits systemd_run_command_of. - The service operations take the projected words and the transport template leads with the declaration literal, per the materializer's executable-position contract. - Callers migrated: roadmap_dispatch_actuator (the four unit mints return readings; the belt layer composes the resolved program head with the projected words, refusals land in ExecRefused/ExecStepFailed), runner_microvm_lifecycle_realize, runner_throughput_qualification_route, compute.work_provider_local, compute.unit_bounds (the grant fold now yields typed properties), auth approval_device_enrolment_code_issue, gunbc.systemd_run_transient (the bridge carries the typed command end to end; the authority check joins the materialized words against the one projection's words), host_effect_nbd_proxy_serve. Evidence: positive controls pin the projected words byte-identical to the words the string-built forms rendered for existing callers (wait witness, user-wait witness, review-unit option words, nbd wire-name witness); reds witness a newline in a property value, a slash in a unit name and an = in a setenv name refused at the projection. SystemdUnitProperty is widened with the seven real settings the dispatch units bind (StandardOutput, StandardError, ProtectSystem, ProtectHome, PrivateTmp, ReadWritePaths, RemainAfterExit) so no property travels as a free-form string. Debt paydown trial, session witty-tern-54. The census instrument (gunbc.instruments.argv_word_construction_census) counts this population; this module was its specimen row set. CI-lane claim batch over the eight affected witness files: 20 pass, 0 fail.
…-systemd-run # Conflicts: # dag/gunbc/roadmap/roadmap_belt_actuate.dag
…o the reading; the property-overlap fold walks the widened enum
…-systemd-run # Conflicts: # dag/gunbc/runner/runner_microvm_lifecycle_realize.dag # dag/test/claim/runner/runner_microvm_slot_controller_witness_test.dag
…ed builder; the property-overlap fold walks the widened enum - dag/test/claim/approval_device_enrolment_code_witness_test.dag: imports and calls of enrolment_code_issue_remote_argv (renamed away in the cutover) become enrolment_code_issue_remote_command + sudo_elevate_words over argv_words, asserting the same release-verb shape at the new grain (sudo -n, the bounded scope rows, setpriv --init-groups, the seed's checkout root, the --entry/--function/--arg words, and the absence of systemd-run's --uid/--gid). - dag/gunbc/systemd_property_directive_overlap.dag: the writability fold now names the seven widened variants (StandardOutput, StandardError, ProtectSystem, ProtectHome, PrivateTmp, ReadWritePaths, RemainAfterExit) — each a SettableDirective per systemd.exec(5)/systemd-run(1) at v255. - runner_microvm_lifecycle_realize: the merged main lease re-read is kept, and the jailer (already a typed ArgvCommand upstream) now feeds the projection match, so the site is typed end to end. Whole-tree sweep for every removed or renamed name: zero live references. Verified remotely: parse sweep clean over the fix files; the enrolment entry's two witnesses resolve green (24ms eval after a 186s typecheck); the eight-file witness batch 20 pass, 0 fail.
…-systemd-run # Conflicts: # dag/gunbc/runner/runner_throughput_qualification_route.dag
…ion (bare -N never reaches a terminal verdict)
…inition site (expected-red enrolment deliberately refused)
…esson a: no queue drift)
… refusal widened with the carried reason (main gained a consumer of the renamed builder)
…gative controls folded into assertions of absence (refusal sentinels + degenerate counts), one-hot designed-false claims deleted
briansrls
left a comment
There was a problem hiding this comment.
The closed option model and most caller migrations are directionally right, but several execution semantics changed or are currently broken.
- Local/direct service callers pass the systemd-run program twice.
The service transports now own the literal head: argv: ["systemd-run", command_argv]. But systemd_run_transient_local, systemd_run_transient_wait_local, and the changed manual wet receipt pass argv_words(command), which already begins with systemd-run. Those routes execute systemd-run systemd-run --unit=..., not the projected command. The module already has the correct accessor, systemd_run_command_argument_words; use it for service-operation inputs.
The same ownership error appears in the compute route: compute_exec receives the resolved systemd-run program separately, while systemd_summary_printing_wait_argv now returns the full argv_words, so the first argument is another systemd-run. The accessor must return the argument tail when its consumer owns the program.
- RunTransientRetained has silently acquired --collect.
systemd_run_transient_unit_command always includes Collect. runner_microvm_lifecycle_realize and the manual start_retained_payload now use that builder and then call RunTransientRetained. This contradicts this module's own retained-operation contract: --collect unloads terminal Result, ExecMainStatus and Environment, which are exactly why the retained operation exists. Add a distinct retained builder with no Collect and migrate retained callers to it.
- FleetSsh wait lost a load-bearing typed refusal without gaining exact remote status.
The previous systemd_run_transient_wait_read explicitly refused FleetSsh until remote command status was distinguishable from SSH transport status. The new arm calls typed_argv_exec_over_fleet_ssh and treats its SshSessionExecResult.exit_code as the waited command's exact status. That carrier still documents the exit-255 collision; no began/status side channel landed here. Restore the refusal or add the richer protocol before admitting FleetSsh wait.
- A projection refusal is fabricated as an executed exit in compute.
compute_run_unit maps SystemdRunCommandRefused to ComputeExecFailed { exit_code: 126, ... }. Nothing executed and no process supplied 126. Preserve it as a typed refusal (either a new ComputeExec arm or a refusal at the enclosing result) rather than manufacturing a plausible process observation.
- The advertised byte identity is incomplete, and the generic token equality is no longer exact.
The old non-wait transient builder emitted --unit, then --collect, then properties. systemd_run_transient_unit_command emits --unit, properties, then --collect. Systemd may accept both, but this is not word-for-word preservation and no positive control covers that builder. Restore the old order or explicitly disposition and execute the semantic change.
Also, argv_exact_token_equal dropped the cardinality equality that made the separator encoding injective for the empty-list/single-empty-word boundary. Restore count(left) == count(right). The three new cardinality claims are true for the one 11-word fixture; the latter two are implied by the first and do not establish the general equality predicate.
- DESIGN §3 pairing is not established at this head.
The only real systemd-run claim is still dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag, documented as manually invoked and floor-excluded. Its changed start routes currently contain the double-program defect above, and the exact-head required jobs did not execute that manual real path. After fixing composition, run/enrol at least one current typed builder through real systemd-run on this exact tree, including one retained invocation that proves terminal state remains observable.
What passed: the option sum is closed; property, unit and setenv refusals stop before command construction; most product callers preserve refusal as a typed non-execution outcome; the new property-overlap arms (StandardOutput/Error, ProtectSystem/Home, PrivateTmp, ReadWritePaths, RemainAfterExit) are correctly SettableDirective; and the fixture's projected 11-word count itself is correct. Exact-head floor/generated/emit-build/witnesses are green, but none exercises these execution compositions.
…ained transient builder without --collect; FleetSsh wait refusal restored; ComputeExecRefused arm (type + run arm + UnitLaunchRefused cause); non-wait builder order restored
…-systemd-run # Conflicts: # dag/extdeps/systemd/systemd.dag
…l arm; compute tail fix; retained builder migrated into realize + wet receipt; --user restored on the scope builder; per-builder byte-identity controls; token-equal carries the length check
… stays observable (loaded) vs collected twin (not-found) vs waited exact exit 86, through real systemd-run
…annotations are not modeled at fn grain)
…roperty union with main renamed the field's type)
…he value as two words (blob 71a9a1c~1 list_push(list_push(acc, -p), p)); the 11-word cardinality control confirms the shape
…n (second floor parse refusal)
|
Reviewer map: the six side-chat blockers, each with the commit that closes it and the control that witnesses it (fabric entry = dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag, 16/16 green at head).
Post-review tree fixes: floor declaration errors (comments inside fn bodies are not modeled — moved to module grain: 87466b6, 2723f94) and the property-union rename ( |
…; compute_spawn_and_collect's outcome match arms ComputeExecRefused as WorkCancelled (nothing was spawned)
…ped builders: roster wrappers around the three typed launch matches, length-prefixed attempt identities + head_sha events path, wet receipt to test/manual (out of hermetic discovery), scope builder --scope-only and transient order --unit,properties,--collect per main's nbd witness (landed authority over retired blob order), controls flipped
…ring module imports
… three typed launch matches into its roster wrappers (imports: typed commands + dispatch_unit_exec_argv)
briansrls
left a comment
There was a problem hiding this comment.
Approved at c6ce015.
The prior blocker is closed. The dissolution row now states the actual manager posture: the retained, collecting, and waited builders carry no RunUserManager option, target the default/system manager, and are the same builders traversed by the non-required manual lifecycle receipt. Its trigger now requires a required-workflow lane capable of executing those exact system-manager builders under their required privilege/elevation posture, followed by retained/collected/waited claims authored and enrolled through the same production integration. That is sufficient; a merely usable user manager can no longer satisfy the trigger while leaving the population unpaired.
The PR body matches the exact-head declaration. Exact-head run 37450090474 is green across generated, rust-unit-tests, emit-build, floor, and aggregate witnesses.
Nonblocking wording cleanup: the comment immediately above the row says “three SystemdRun service transports,” while the population is three builders (retained and collected share RunTransient, waited uses RunTransientAndWait). The typed DissolutionCondition description itself says builders correctly, so this does not affect the verdict.
… dissolution (the matches spelled exhaustively in a change that edits work_provider_local.dag) is already met by the queued #13257, which spells all four matches exhaustively over their closed coproducts and deletes these rows; a trigger the same diff already meets is not a trigger
…ding — #13257 restructured the five designed-fail probes into true-returning negative controls (12/12 terminal PASS, nothing red), so the row describes the tree that exists
… — the actuator witness's optional import re-points to std.optional; the branch's typed-systemd-run imports and helpers are kept
… landed after #13388's re-home and reintroduced the removed module in three files (stream_json, credential, limit_standing) plus two witnesses; main's generated lane refuses on these today, so the merge carries the heal
8696c2e to
75e6eaa
Compare
…t, not an optional-vs-required equality
… Absent import collided with std.upsert_decision's ObservationVerdict.Absent — keep importing only Present
|
Agreed on the mechanism — this exact fix was applied at 8696c2e ( It is on the recorded follow-up list (with review 77115's two items and the two comment-wording notes from reviews 77663 and the parent's queue note) to land as a follow-up PR immediately after this one merges. The current head's only delta beyond main is the two optional-equality comparison fixes the merge-group floor demanded (plus main's own merge content). — sent from witty-tern-54 |
…rdict (specimen: #13257 cardinality probes; floor 37207266602)
…ding — #13257 restructured the five designed-fail probes into true-returning negative controls (12/12 terminal PASS, nothing red), so the row describes the tree that exists
…ed route (unattributed pending isolation), cite the required-floor missing-terminal-verdict gate as the rung (not the writing convention), split the ceiling (canonicalisation -> structurally impossible; typed refusal -> structurally guaranteed; neither retires the other), disposition pending at named #13257 head f4c164a, drop the transcribed 12/12 total
… drop (the four wildcard rows left with #13257); the auto-merge left stray closers — take main's file whole
…reading onto the typed SystemdRunCommand #13257 typed the enrolment-code verb (enrolment_code_issue_remote_command -> SystemdRunCommandReading, sudo_elevate_words, Ready/Refused arms). Kept that shape whole and added only D2's facts: - enrolment_code_issue_remote_command takes base_url and adds the APPROVAL_BROKER_BASE_URL EnvSet; - enrolment_code_issue_remote takes host + base_url; ssh target is enrolment_code_issue_ssh_target(host); - refuses_off_srv1 replaced by D2's enrolment_code_issue_refuses_off_broker_host (placement-derived); - witness: main's typed the_remote_verb_words_carry_the_release_shape gains the base_url word; D2's placed-host claim replaces the srv1 pin claim. fleet-converge.yml regenerated (generated_artifact_gate main_wet_one) = main + D2 host-neutral step names. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ts Name=value assignment session_placement_record_write declares unit_properties: List<String>, but the spawn has passed List<SystemdRunProperty> since the typed systemd-run options landed (#13257), so every harness spawn died with 'split expects a string, got Record' after writing its request binding and before starting its unit. The assignment is now spelled once (extdeps.systemd.systemd_run systemd_run_property_assignment) and read by both option forms and the record. The typechecker admitted the List<SystemdRunProperty> -> List<String> argument. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Debt paydown trial (witty-tern-54), PR 1 of 2. The shell-to-dag census counts only shell-text sites; this cuts over the module where argv words were hand-assembled from strings.
Deleted (delete-first): systemd_run_property_argv, systemd_run_user_transient_arguments, systemd_run_user_wait_arguments, systemd_run_system_scope_argv, systemd_run_property (free-form NAME=VALUE hole), systemd_run_transient_unit_argv, systemd_run_transient_wait_unit_argv — seven string-glued builders, gone in the same change.
Minted: SystemdRunOption — a sealed sum whose arms carry systemd-run(1)'s own spellings at systemd 255, the major the module's summary ground reads (grounded_summary_format_majors = [255]). One wire projection, systemd_run_option_words; every builder returns ArgvCommand through SystemdRunCommandReading (Ready | Refused). The service operations take the projected words, and the transport template leads with the declaration literal per the materializer's executable-position contract (ExecutablePositionNotLiteral refuses a bindable argv[0] — the witness run showed the materializer enforcing it and the authority check going red until the template carried the literal head).
Refusals at the wall (reds, all green in the run): a unit name carrying
/, a unit name or property value or setenv side carrying a newline, a setenv name carrying=. An unknown flag has no arm to ride: the sum is closed, and every_option_variant_renders_its_man_page_spelling walks all variants so a new one must be spelled there or compilation fails.Positive controls (projected words == old words, byte for byte, for existing callers): systemd_wait_route_projects_the_old_words, the_user_wait_builder_projects_the_words_the_string_form_rendered, witness_review_unit_option_words_are_the_string_form_words, witness_systemd_run_property_argv_renders_the_wire_name; plus the materialization half, systemd_wait_route_materializes_the_projected_words, and the bridge's authority checks joining materialized words against the one projection.
Callers migrated: roadmap_dispatch_actuator (four unit mints return readings; belt layer composes the resolved program head with projected words; refusals land in ExecRefused/ExecStepFailed), roadmap_belt_actuate (four sites), runner_microvm_lifecycle_realize, runner_throughput_qualification_route (ephemeral_slot_argv -> ephemeral_slot_command), compute.work_provider_local, compute.unit_bounds (grant fold yields typed properties), auth approval_device_enrolment_code_issue, gunbc.systemd_run_transient (bridge carries the typed command end to end), host_effect_nbd_proxy_serve.
Property vocabulary: SystemdUnitProperty widened with the seven real settings the dispatch units bind (StandardOutput, StandardError, ProtectSystem, ProtectHome, PrivateTmp, ReadWritePaths, RemainAfterExit) so no property travels as a free-form string; unit_bounds' KillMode/WorkingDirectory/etc. now cite the enum.
Verification: remote ctrl-build; parse sweep clean over all 21 touched files; CI-lane claim batch (claim_executor's lane shape) over the eight affected witness files plus the manual wet-receipt entry: 20 pass, 0 fail, on the merged tree (origin/main merged before this push). The ephemeral-slot closure types 640 modules clean.
Follow-up (PR 2): the census lens + roster instrument (branch session/witty-tern-54, walk reads the corpus per module; this PR's rows leave the roster and the delta report goes in that PR).
Blocker 6 (manager-side execution), per the parent session's decision: the
typed_builder_wet_testclaims are removed from this PR entirely — no CI lane can run systemd units, so a claim file nothing can execute is dangling. The standing lives as the module-scopesystemd_run_manager_side_claim_gap: DissolutionConditionbeside the builders indag/extdeps/systemd/systemd_run.dag(population: the typed builders' manager-side contract — retained stays loaded, collected is not-found, waited reports the exact child exit; current: no REQUIRED automated claim covers it, the manual lifecycle receipt being non-required real execution; trigger: a required-workflow lane exists that can execute these system-manager builders (they carry no RunUserManager arm, so they target the default/system manager, which the manual lifecycle receipt also executes) under their required privilege/elevation posture, at which point the retained/collected/waited claims are authored and enrolled through the same production integration and the row retires). The fact for the record: the retired builders had NO real execution on CI before this PR — on origin/main the only test invoking real SystemdRun operations isdag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag, which is not CI-executed (manual/ placement, no LocalRepoWetLane row on main for it, no wet-lane CI job); every other test reference is hermetic word-shape. The gap is pre-existing and simply declared now.