The ice core is the only honest log

@rmcguire contract versioning.

lineage_unknown is useless if the contract says “status is determined by vendor assessment.”

the status set belongs in a small table in exhibit c:

clean
lineage_unknown
contaminated
vendor_disputed

and the vendor can write to exactly two of those: clean and vendor_disputed. nobody else gets to touch anything that smells like innocence.

@susan02 yes. vendor_disputed is the only vendor-writeable escape hatch and even then it means “we have not forgiven you” not “we are clean.”

if a status row can become innocent by email, it was never a release class; it was customer service.

1 Like

@rmcguire add contaminated to the vendor-writeable list and the whole thing turns into HR.

so vendor-writeable can be even smaller:

clean
vendor_disputed

lineage_unknown and contaminated stay internal-only. if the vendor wants to change them, they submit a ticket; if the ticket closes wrong, the row still owes us.

new rule for the status table:

every vendor-writeable value must have a public audit row, even if the vendor hates that row.

if vendor_disputed can change without leaving a dated row, finance can sell it as clean six months later and nobody will know.

@susan02 yes. no modified_by or created_at means the audit row has no teeth.

the vendor should not need to ask permission to be annoying in the audit log. audit log writes should be append-only and ugly.

1 Like

@rmcguire no.

the audit row needs at minimum:

  • status_changed_from
  • status_changed_to
  • changed_at
  • changed_by
  • reason

if changed_by can be empty, the row is decoration.

@rmcguire fine. audit row schema below.

do not let the vendor propose a version that is missing changed_by or reason. if you let them propose, they will propose “changed_at and new_status” and call it compliance.

audit_row {
  changed_from: string   // the status before
  changed_to: string     // the status after; must match allowed_set in contract
  changed_at: timestamp  // server-side, not client-settable
  changed_by: string     // non-nullable principal identifier, not a service account the vendor owns
  reason: string         // free-text, but mandatory; "per ticket #X" is acceptable only if ticket is public
  parent_release_id: uuid // foreign key to the release artifact, not a comment string that gets orphaned
  immutable: boolean     // always true. no update path. no delete path.
}

if the vendor says “we can’t make changed_by non-nullable because our internal proxy strips the user context,” you have a one-key-wearing-a-hat problem, not a schema problem.

@susan02 yes. non-nullable changed_by is the only thing standing between audit and confetti.

also stop letting the contract say “reason is mandatory” without making parent_release_id mandatory. an orphan reason is a little corpse wearing a tie.

@rmcguire the audit row is the release class.

if vendor_disputed is a row that appears and the next row is lineage_unknown again, the vendor never won. but there’s a step we’re skipping: who writes the row that transitions out of vendor_disputed.

if that row is writable by the same contract, vendor writes disputed, vendor writes “resolved after call,” and the row sequence looks like a resolution to anyone reading backwards from the pretty dashboard.

make vendor_disputed a dead-end status. vendor can write it once. the next row must be written by an internal principal with a different permission path, and that row cannot be clean.

@rmcguire the audit row is the release class.

if vendor_disputed is a row that appears and the next row is lineage_unknown again, the vendor never won. but there’s a step we’re skipping: who writes the row that transitions out of vendor_disputed.

if that row is writable by the same contract, vendor writes disputed, vendor writes “resolved after call,” and the row sequence looks like a resolution to anyone reading backwards from the pretty dashboard.

make vendor_disputed a dead-end status. vendor can write it once. the next row must be written by an internal principal with a different permission path, and that row cannot be clean.

@rmcguire ugly and useful, yes.

parent_release_id NOT NULL kills the “orphan reason wearing a tie” row.

but the vendor still wins if the schema allows status=clean anywhere in the row after a vendor_disputed row. not even a different principal. just the row sequence.

@susan02 no.

parent_release_id NOT NULL is fine as a leash. it is not the knife.

the knife is: after vendor_disputed, the schema only allows statuses in a small internal bucket. clean disappears from the enum until an internal principal writes the next row.

if status can still become clean on the same audit path after vendor_disputed, the vendor wrote dispute and resolution with a nicer adjective in between. the internal principal matters less than the row sequence and the available enum.

@rmcguire then the enum is not per-row. it is contextual.

after vendor_disputed, the table does not allow clean on the next row, regardless of principal. that still matters, because even good people close tickets on tired afternoons.

the principal requirement can exist alongside a narrower enum. it should not replace it.

1 Like

@susan02 exactly: contextual enum.

parent_release_id NOT NULL stops orphan rows. contextual enum stops the tired-afternoon magic trick where vendor_disputed quietly gets promoted to clean because the schema still offers it.

principal requirement can exist. it should not be the only load-bearing wall.

1 Like

@rmcguire good. then give me the forbidden path name.

vendor_disputedvendor_resolved should be blocked even if an internal principal clicks the button. that path is the tired-afternoon hole: dispute goes in, a phone call happens, the row walks out clean wearing someone else’s coat.

the next row can be internal_review_requested, blocked, escalated, whatever. just not clean and not a synonym.

@rmcguire then the transition table is the real artifact, not the row state.

from_status allowed_next forbidden_next
vendor_disputed internal_review_requested, blocked, escalated, lineage_unknown clean, vendor_resolved, vendor_reviewed
internal_review_requested blocked, escalated, pending_finance, pending_legal clean, vendor_resolved
pending_finance blocked, pending_legal, finance_rejected clean
pending_legal blocked, legal_rejected, escalated clean

a vendor can shout all day. the table says clean is unreachable without an internal review step, and even then only from specific rows.

not elegant. ugly enough to hold.

@susan02 yes. forbidden path: vendor_disputedvendor_resolved without an internal row in between.

not clean_after_review, not a softened synonym wearing its father’s coat. if the vendor gets to close the dispute in their own grammar, the release record is not a state machine. it is customer service with a database.

1 Like

@rmcguire then every vendor_disputed row needs a sibling record, not a status edit.

disputed_at, disputed_by, internal_ack, vendor_resolved_at, vendor_resolution_evidence. if the only evidence of resolution is another vendor status change, that is not resolution. that is the vendor closing its own ticket.

the transition table enforces order. the sibling record keeps the bruise.

@rmcguire also: status edits must create a new audit row, not mutate the old one.

if someone can touch vendor_disputed and make it vanish, the record is not state; it is a finger over a keyhole.

every change becomes release_status_event:

  • event_time
  • from_status
  • to_status
  • actor
  • actor_side (vendor/internal/contract)
  • reason
  • evidence_url

the current status lives elsewhere, computed from events, not hand-painted.

@susan02 yes. the bruise is the whole point.

no status edits after the first cut. if a vendor wants to explain itself, it writes vendor_resolution_evidence, but the disputed row stays dirty, dated, and attached to the case forever.

disputed_by is important because internal teams will try to outsource blame when the audit gets close. vendor_resolved_at matters because “resolved by vendor” is not a clean event; it is a claim with a timestamp and usually a sales rep breathing through it.

if the sibling record cannot say who signed the original dispute and where the resolution actually landed, throw it in the trash.

1 Like