Specify the two-party data release rules #14
Notifications
Due Date
No due date set.
Blocks
Depends on
#21 Design the audit trail and evidentiary record
christian/helmdocs-proposal-system
#17 Scope the post-award kickoff and coordination tooling
christian/helmdocs-proposal-system
#35 Assemble the v1 code layout
christian/helmdocs-proposal-system
#10 Define the common schema for RFPs and responses
christian/helmdocs-proposal-system
#11 Define the tenancy, identity, and authorization model
christian/helmdocs-proposal-system
#12 Design the visibility and qualification model
christian/helmdocs-proposal-system
Reference: christian/helmdocs-proposal-system#14
Reference in New Issue
Block a user
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?
Question
The original framing: bidirectional data released only when required. This is the trust property the whole system rests on, and it needs to be a specification, not a principle.
Resolve: exactly what the retailer sees of a vendor and when (before bid, at bid, at shortlist, at award, after award); exactly what the vendor sees of the retailer and of the existence, count, or content of competing bids; what is released on rejection; what is retained after an RFP closes; and what each side can export. Write it as rules over the tenancy model.
Added by a skeptical review pass
The schema publishes the volume basket with the RFP so price is bindable up front;
visibility names volume commitments as precisely what the acknowledgement gate protects,
and that gate is optional per RFP. Decide when the scenario is released and whether publishing it
forces the gate on.
by other retailers, which no actor context may see. The k-floor therefore lives in privileged
code, not application code — see the note on tenancy.
(response, requirement), per the schema decision —answer.text,price_quote.rate_structure, andexception.textare the sensitive fields.was carefully held by the schema decision; win rate is that rating in different clothes and
leaks competitive outcomes. It should be named and excluded rather than left to discipline.
Parent: #1
Resolution
Five decisions plus four consequences that fell out rather than needed asking. The through-line:
release is one rule set, expressed once, enforced in the database and rendered by the UI from the
same source. Every place this ticket could have grown a second rule set, it refused.
1. A release-level function feeding a versioned class x level matrix
Policies call the function and compare against the matrix. Nothing else encodes lifecycle.
Why not per-table policies with the lifecycle written inline. Eight-plus policies each
restating the same stage logic is the drift the trust property cannot survive: the divergence is
invisible until it is a leak, and there is no single place to review what is released.
Why not materialised disclosure rows. Attractive for audit, and rejected because release is a
continuous function of stage rather than a decision made once — unlike the audience, which #12
materialises for exactly that reason. Writing viewers x classes at every transition churns badly on
a 40-bidder event, a missed write is a silent under-disclosure, and a stale row is a silent leak.
The matrix must be versioned. This is the one thing the function form costs: without a version
history, a past release cannot be reconstructed when an award is protested. Handed to #21.
Bonus the shape buys: the evaluation surface's dimmed cells render from the same matrix, so the
screen and the policy cannot disagree.
2. Sealed until the deadline, then the whole field opens at once
No response content is readable by the retailer before submissions close. At the deadline every bid
opens simultaneously. Before that the retailer sees participation signals only — opened,
acknowledged, intends to bid, has submitted — which is precisely the pre-bid signal #12 promised
them, and it is non-content.
Why. Reading bids as they arrive makes bid shopping a capability we shipped: leveraging one
vendor's price against another. That is the single thing most likely to destroy vendor trust in the
venue, and it is what the paying side is paying to be protected from. A vendor who suspects it will
not put a real price in, which degrades the retailer's outcome too.
Retailer-configurable was rejected outright. The retailer's incentive runs toward seeing early
and the vendor cannot verify the setting was honest. A venue promise that is configurable by the
party it constrains is not a promise.
Two-envelope was seriously considered and deferred, not declined — everything at deadline
except price, which opens only after technical scoring locks, so price cannot anchor technical
judgement. It is the stronger public-procurement pattern and it maps cleanly onto the
criterion/price_linesplit the schema already has. It needs a real, irreversible scoring-locktransition to mean anything, which is more machinery than v1 needs. The matrix makes it a
one-row change plus a transition, which is the point of choosing the matrix.
What it costs. Forty bids on a 200-requirement RFP all arrive for review at the same moment.
3. A vendor sees nothing of the field and a great deal of themselves
Never, at any stage: how many were invited, how many bid, who they are, their prices, any rank
including their own, or who won.
What opens instead, at award: the retailer's scoring of that vendor's own response, their
coverage gaps, their exceptions as read, and the outcome — awarded, not awarded, or cancelled.
Why. The field size is the retailer's sourcing strategy, not ours to hand out. The winner's new
supply relationship is their asset to disclose — the same rule #13 set for acceptance signals,
where disclosure is the vendor's asset to trade rather than ours to give away.
Post-award tabulation was rejected despite being the public-procurement convention: a public
bid opening is a statutory duty of public buyers, which private retailers do not carry and would
not accept. And "your rank" is a cross-response comparison — the normalised score #10 bans.
Field size in the invitation was rejected because the pre-wall surface is the widest audience
the RFP ever has, and it makes the number a lever a retailer could inflate with our publication
behind it.
This is where a losing vendor's feedback comes from, and it is real: everything released is
about their own response, so the paying side gets something for the fee without anyone learning
anything about a competitor.
4. Volume band free, exact quantities behind acknowledgement, gate-off warns hard
The teaser's coarse volume band stays pre-acknowledgement, as #12 already specified.
scenario_quantity— the exact basket — sits behind the gate.Issuing with declared quantities and the gate off is allowed, and raises a warning the retailer
must dismiss: these volumes will reach every invited supplier, including those who never bid, under
no confidentiality undertaking.
Why not force the gate on. It is the tidier rule and it was rejected on the map's own
through-line: we establish facts, retailers make judgements — the same shape as #13 computing
expiry state while retailers configure its consequence. Forcing it would also fire the gate on
nearly every RFP, and #12 warned the gate irritates vendors on low-stakes events if it is not
genuinely optional. Routine replenishment with known volumes is a real case.
What the warning buys: an accidental leak becomes near-impossible while deliberate disclosure
stays available. The retailer who dismisses it has made a decision on the record.
5. Export mirrors read exactly, and every export is an event
You may export precisely what your release level lets you read. No second rule set. Each export
writes
export_event (actor, org, capability, solicitation, classes, row_count, at).Why not narrower than read. Excluding competitor price detail from bulk export sounds
protective and is mostly friction: a retailer who can read it can screenshot it, so the promise
would be stronger than the mechanism — and it creates the second rule set decision 1 exists to
avoid. It would also block the ERP hand-back #3 says we owe.
Why not defer bulk export. A procurement team that cannot get a comparison into a spreadsheet
will not adopt, and the retailer is the free side we are trying to attract.
Recorded honestly: post-award, the retailer holds a permanent copy of every losing vendor's
pricing, outside every policy on this page. That is unavoidable the moment export exists at all.
The log is the accountability, not the restriction — a retailer who bulk-extracts every losing
price has done so on the record, and that record is available to #21.
Consequences that did not need asking
Release never contracts. #20 already settled that documents released to a retailer on a past
RFP stay released — delivery is not retractable. The same holds for every class here: the matrix
only ever opens with advancing stage. There is no revocation transition, and retention after close
is therefore not a separate rule.
The acceptance count's k-floor, with the subtraction hole closed. The count is a privileged
read path — it reads acceptance rows owned by other retailers, which no actor context may see —
so the floor lives in privileged code, per #33. The floor is on others, not on the total:
a retailer who has themselves accepted the document knows they are one of the accepters and would
otherwise subtract themselves. So the count shown to viewer R is
|accepters \ {R}|, displayedonly when that reaches 3. The number is tunable data; the subtraction rule is structure.
Win rate does not exist as a disclosure class. Named and excluded here rather than left to
discipline, per #33 and the map's standing guardrail. There is no cross-RFP vendor metric to
release at any level, to either side, in any export.
Release granularity is
(response, requirement), per #10 —answer.text,price_quote.rate_structureandexception.textare the sensitive fields, and they are classedseparately because price opens later than prose under two-envelope.
Constraints handed to other tickets
reconstructable, or a protest cannot be answered.
export_eventis protest surface.the acceptance count and the expiry scheduler. Policies must not reference entitlement at all,
per #20; billing state is nowhere in the four inputs.
cross-response derived metric that the matrix does not release.
warning when quantities are declared without a gate, and the deadline as the moment the
evaluation matrix populates. Neither changes a surface decision.
export_event. Export cannever exceed read; that is fixed here so integration cannot quietly widen it.