spec(db): verify ServiceSpec probe/sockets/require_ready round-trip through spec_json — add regression test + doc contract #90
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_proc#90
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What this checks
Before this work was committed, a concern was raised: do the three new
ServiceSpecfields (probe,sockets,require_ready) require a SQLite schema migration?The check passed: no migration is needed.
Why no migration is needed
ServiceSpecis persisted as a single JSON blob in thespec_json TEXT NOT NULLcolumn of theservicestable. The schema has not changed — the column was there before; only the set of keys the JSON blob may contain has grown.New fields and their serde defaults
probeOption<ServiceProbe>#[serde(default, skip_serializing_if = "Option::is_none")]None; absent whenNoneon writesocketsVec<String>#[serde(default, skip_serializing_if = "Vec::is_empty")][]; absent when empty on writerequire_readybool#[serde(default)]false; always writtenAll three use serde's standard absent-key → default-value rules. A
spec_jsonblob written by any older version of the code (which lacks these keys entirely) deserialises cleanly withprobe = None,sockets = [],require_ready = false.Round-trip invariant
A
spec_jsonblob written by a newer version of the code:probewhenNone— identical to the old formatsocketswhen empty — identical to the old formatrequire_ready: falsefor services that never set it — older code would reject this as an unknown field only if#[serde(deny_unknown_fields)]were present; it is not present onServiceSpecSo old code reading new blobs also survives.
Relevant code locations
crates/hero_proc_lib/src/db/service/model.rs:148—spec_json TEXT NOT NULL, unchangedServiceSpecstruct and new fields:model.rs:219–254ServiceProbeandServiceProbeKind:model.rs:257–295crates/hero_proc_server/src/supervisor/service_state.rsdeclare_ready/clear_ready):crates/hero_proc_sdk/src/ready.rsSpec: what "round-trips correctly" means (acceptance criteria)
servicesrow written before these fields existed deserialises without error;probeisNone,socketsis[],require_readyisfalse. The supervisor treats the service as having no probe, no socket invariant to check, and no readiness requirement. Behaviour is identical to pre-change.probe,sockets, andrequire_ready: trueserialises all three keys intospec_jsonand deserialises them back with the exact same values.spec_jsonblob with noprobekey, nosocketskey, andrequire_ready: false. On next read the struct matches the in-memory representation before the write.ALTER TABLEneeded: confirmed by code inspection — thespec_jsoncolumn predates this change and no DDL migration was added.ServiceSpecdoes not carry#[serde(deny_unknown_fields)], so if further fields are added in the future the same argument holds.What this issue is NOT asking for
This is not asking for a new migration mechanism, a versioned schema, or any code change. It is asking for:
model.rsthat constructs aspec_jsonstring that omitsprobe,sockets, andrequire_ready, deserialises it, and asserts the defaults. This prevents a future#[serde(deny_unknown_fields)]annotation or a struct rename from silently breaking the invariant.ServiceSpec(or next to thespec_jsoncolumn DDL) that makes the intentional absent-key contract explicit, so future contributors know they must preserve it.References
crates/hero_proc_lib/src/db/service/model.rs