Config writes and activation outcomes (plan C2) - #37
Merged
Merged
Conversation
A failed reload, source replacement or source creation now reports written and committed (true, false or null) with active_generation_id, and the error contract lists the shared outcome codes. A created source is never removed after it is stored. Config writes are described as a transaction on the configuration store rather than a file rename, and PUT lists the listener-settings and restart-required refusals.
… provider writes Readback always returns source content when config.available is true, so the content flag goes and ConfigSource.content becomes required. config.max_bytes must leave room for the request envelope under the JSON body limit. Node and provider create and delete may return 202 with an operation whose result is the created resource or the deletion count.
Zakkaus
force-pushed
the
fix/contract-config-outcomes
branch
from
September 28, 2026 16:36
79e3075 to
073f3f4
Compare
A create whose activation reports committed false removes the source again, so the store matches the active configuration and no source is left that the API cannot address. written then reports false, or true when the removal failed. With committed true or null the source stays. Replacement never removes anything, so PUT can repair a replaced source.
…max_bytes envelope
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Plan PR C2: config writes and outcomes. Decisions Q2, Q12 and Q15 of the plan and its F2 paragraph apply. honk references are to
Glassyiris/honkfeat/native-api253c342,crates/honk-core/src/native_api/. dae has no native REST listener yet, so each dae line says what an implementation would use.F2: activation outcomes
error.details.committed:false(previous generation still active),true(new generation active,active_generation_idnames it or is null, the request still fails), ornull(unknown; read backGET /runtime).writtenis a separate detail: whether the store holds the change. The shared codesreload_rejected,reload_degraded,supervisor_reconciliation_failed,activation_unconfirmedandstore_unavailableare listed with theircommittedvalue. An operation carries the code inerror.code; a synchronous 503 carries it indetails.stage.ErrorCodeand the embedded-error sentence name these as the exception to "adapter-defined codes". reload.md and configuration.md drop "the previous generation remains active" as an unconditional claim.Rejectedon a plain reload getscommitted:false(completion.rs:181, today only the replace path passes it);Unconfirmedreports codeactivation_unconfirmedwithcommitted:null(it isengine_unavailableinreason(),completion.rs:11-14, and has no details on the file-store path); the store failure keepsstore_unavailableand addswritten:false(:103-108,:150). Synchronous node and provider writes:management_error(completion.rs:27-38) addsactive_generation_idforDegradedandReconciliation, and the store failure (coordinator.rsrecorderror) usesstage: store_unavailableinstead ofstage: store.cmd/run.gorestorePreviousFreshDatapathGeneration), which iscommitted:false; a failed restore iscommitted:null. dae has no degraded-but-active state today, so it never reportstrueexcept throughstore_unavailableif it adds a database store.F42: a created source is removed only when activation did not commit (Q15)
createConfigSource: a failure before the source is stored creates nothing. Withcommitted: false(reload could not start or was rejected, previous generation active) the server removes the created source only if the store still holds it with the same identity and content, and reportswritten: false; a replaced or modified source, or a failed removal, is kept and reported as the cleanup conflictwritten: truewithcommitted: false, and has to be reconciled outside the API. Withcommitted: true(active, degraded) orcommitted: nullthe source stays; withnullthe client reads backGET /configandGET /runtime. Replacement never removes anything, so a replaced source keeps its accepted ID and PUT can repair it. honk-mapping.md's create row says the same.committed: false(completion.rs:93-102) and the dispatch-failure path never activated, butCreatedFile::removecompares only the inode it created (config_write.rs:277-307), so an in-place edit that keeps the inode is deleted. It must also compare the content it wrote and keep the file on a mismatch.os.SameFilestill matches the file it wrote, delete it and reportwritten:false; otherwise, or if the delete fails, keep it and reporttrue. If the restore after a failed cutover also fails (committed:null), keep the file.F13: store-neutral write rules
If-Matchagainst the stored hash, one transaction (readers see old or new bytes), serialized hash check, validation and commit, recheck at commit (412). A store that records only after activation is allowed;writtenthen says whether it did. Temp file, rename and file mode move into a "file store" sentence. The 412 texts in errors.md,common.yamlandconfig.yamlsay "stored" instead of "on-disk". honk-mapping.md evidence rows about the old pinned revision are left as they are.store.rs) already records after activation (completion.rs:130-162).F10: PUT refusal reasons
restart-requirederror diagnostic per setting the engine applies only at startup. Create lists the same refusals. Dry-run validation reportsrestart-requiredas a warning.ConfigSource.writablesays it is false for a source holding listener settings or secrets and while the store cannot accept writes.read_only_reasonis not added:kind,writableand the capability switches already give the reasons dae would have, and the rest are honk policy that the new text describes.coordinator.rs:988-1023,config_write.rs:459-462,config/coordinator/validation.rs:64-89,config.rs:477-488).restart-required.F7:
config.contentremoved (Q2)discovery.yaml, capabilities.md, configuration.md andconfig.yaml.config.availablemeans every returned source carriescontent(listener-secret values masked), soConfigSource.contentis required. The "redacted" examples now show masked content andwritable: false. Tests updated: nocontentin the capability, a source withoutcontentis invalid."content"from the capability object (types.rs:166). It already returns content on every source (config.rs:565-580).F36:
config.max_bytesbelow the body limitconfig.max_bytesmust be less thanlimits.max_json_body_bytesminus the request envelope; the body limit still applies, and escaping can exceed it. The capability example uses 61440 with a 65536 body limit, and a test checks the example against a create envelope whose permitted path has the longest JSON encoding (1020 quotation marks plus.dae).types.rs:166advertisesMAX_BODY_BYTESfor both; advertise a smaller value.F22: node and provider writes may be asynchronous (Q12)
node_create,node_delete,provider_create,provider_delete; the create result is the created Node or Provider (with its ID), the delete result is thedeletedcount. A failure after the change is stored uses the F2 details. node-latency.md, providers.md and operations.md describe it; the 202 responses have examples.cmd/run_reload_worker.go), so the natural implementation is 202 with an operation that completes when the reload does.Revision after the dae compatibility and copy reviews
written: true,committed: false. honk: follows in HA (see F42,CreatedFile::removechecks identity only). dae:os.SameFileand a byte comparison with the written content beforeos.Remove.resources.providers.create_unfetched. When true the provider starts unfetched; otherwise the backend may fetch during activation and returns the actual state. honk: follows in HA, advertisecreate_unfetched: trueintypes.rs. dae: leave it false and let its normal reload resolve the subscription.404for their IDs. honk: matches (in-process operation store). dae: the in-process API dies with the daemon, which this allows.409 state_conflicton a server that does not queue, an out-of-band edit before commit fails it with412, one after commit waits for a later reload. honk: matches (one coordinator worker runs eachWorkthrough activation,coordinator.rs:114; commit recheck:1038-1060). dae: hold a write mutex across validation and reload and reload the validated sources instead of rereading disk.202means accepted, atomic rename kept for file stores (sol copy fix 3). honk: matches (file store first, database store after,completion.rs:133-165). dae: file first.428/400onIf-Match,415,413,400), replay lookup,412,422,409; exceptions: creation's path409precedes validation, the listener403is decided during validation, and group PATCH checks415first and reports a missing or malformedIf-Matchafter the replay lookup, so a retained replay succeeds without the header. honk: matches (config/http.rs:126-209,operations.rs:206-273,config/coordinator.rsprepare_create; group PATCH pergroups.rs:334-450groups::patch). dae: same order in the handler, with the group PATCH exception.restart-requirednow means a requested change that cannot take effect through reload, compared with the active configuration. honk: matches (restart_diagnosticsagainst the active config,coordinator.rs:1019). dae: compare against its running config.config.yaml:11-12) is left to C3 (docs: access model, geodata lifecycle and one SSRF policy (C3) #38). This PR adds no visibility rule; capabilities.md now links to configuration.md instead of restating it.503; finished keys, including synchronous200replies, are retained from completion; new optionalresources.operations.max_replay_keysbounds early eviction, oldest-finished first. honk: matches #56 (32 records, 1024 tombstones,operations.rs:24-26,523-546); follows in HA: advertisemax_replay_keys: 1024. dae: a small in-process registry.410. honk: matches (each list has its own snapshot store,flows.rs:483-492). Seen while checking: honk also returns410for a filter mismatch (flows.rs:491) where C1 says400; that is outside this PR.max_bytes: at most the body limit minus the compact UTF-8 JSON envelope, taking for POST the permitted path with the longest compact JSON encoding (each"or\in the path encodes to two bytes); escaped content can exceed the body limit, which then applies. The "fits" claim is gone. honk: the HA line under F36 stands.stageanddurability_confirmedare defined in the outcomes table. honk: matches (management.rs:147-158,types.rs:174-178).supervisor_reconciliation_failedandstore_unavailableapply only to engines with a separate supervisor or a record-after-activation store, so dae need not produce them.reload_rejectedstays as the fifth code: honk emits it (completion.rs:15).GET /configand single-source examples now saybytes: 90, the length of their content.configuration.html#Editingbecause the page has twoRequestheadings.Check:
npx -y yarn@1.22.22 check:contractpasses (74 tests).