Retail RFP transmission system — v1 build spec #1
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?
Destination
A v1 code layout committed to this repo: the directory and module structure, the domain model
as real schema files, the permission boundaries expressed as code, and the seams where the
undecided parts will land. Not a running product — the skeleton a build can start from without
being redesigned.
Everything on the map serves that. Which means the tickets that determine structure — schema,
tenancy, visibility, data release, vault, entitlement — gate the destination, while the ones that
determine behaviour — ranking internals, audit retention, kickoff scope, retailer integrations —
can land as modules with stubs and be resolved after the layout exists.
v1 is a demoable proof of concept. Amended after real completed vendor responses proved
unobtainable — the schema is designed against a synthesized response corpus written to
solicitations we genuinely hold. The RFP half rests on real artifacts; the response half does not.
Lightweight but with teeth: the corpus must stress the schema, not flatter it. Nothing built on this
schema should be treated as production-ready until at least one real completed response has tested
it. See Obtain real completed vendor responses.
The destination has been reached. The v1 code layout is committed - see
Assemble the v1 code layout. Every remaining ticket is behaviour landing in an existing
seam, v1 content filling a vocabulary the surfaces already render, or the demo chain. Nothing left
on this map redesigns the skeleton.
One demo artifact is in scope, by author direction. A clickable POC prototype for the
first internal meeting - the six surfaces styled and navigable over synthesized corpus data, with
no RLS, scheduler, extraction or billing behind them. It sits just past "not a running product" and
is admitted deliberately rather than by drift: it is a design artifact built from the same surfaces
and corpus the layout uses, not a first build. See
Build the clickable POC prototype for the internal demo. The code layout remains the
destination; the prototype is allowed to be throwaway.
Market validation is deliberately not part of this map. The map assumes demand and resolves
design. See Out of scope.
Notes
Domain. A two-sided RFP marketplace. Large retailers (Walmart/Target scale) distribute RFPs
and evaluate bids for free; revenue comes from a tiered vendor-workspace subscription. The free side
is the retailer; the paying side is the bidder.
There is no free vendor tier. Amended by author direction — a $100/month floor gates
participation entirely, with Pro at $350/mo and Enterprise at $1,000/mo. This reversed
What do bidders actually pay for? and decision 1 of entitlement; both carry
supersession notices. The cost is recorded rather than resolved: it puts the two sides in tension
by construction, and the supplier-diversity exposure is a retailer-side adoption risk. See
Specify the vendor pricing and tier structure.
This is a HelmDocs product. Settled by author direction: a sibling to the existing
bidder-tools product under one brand, not its own. The design system is therefore extended rather
than authored -
packages/config/shadcn.cssinnovelpad-desktopalready carriesdata-brand="helmdocs"tokens. The cost is recorded rather than resolved: the venue now carriesthe bidder-tools brand, so a retailer may reasonably ask why the company selling bid-writing AI runs
their evaluation. That argument can no longer be made by brand distance and must be made by
mechanism - sealed bidding, the human commit, the model bar - which makes those mechanisms
demo-critical. See the component system.
The central inversion. The artifact worth standardizing is the response, not the RFP. Bid
ranking works because responses are machine-comparable, not because RFPs are. So the paying side
produces the value the free side receives — the mirror of Ramp, where free software generates the
monetized asset.
Skills each session should consult.
/grill-mefor decision tickets./to-issuesonce thespec is locked. This repo has no
/research,/prototype, or/domain-modelingskill installed —substitute a general-purpose research subagent for research tickets and drive prototypes inline.
Standing preferences.
novelpad-desktopmonorepo(
packages/ai/src/agents/*,packages/core/src/solicitation-extraction/). Reuse the thinkingand the prompts; v1 is a separate deployable, so do not reuse the runtime.
Postgres row-level security, with actor context carried in transaction-scoped session GUCs.
Every subsequent ticket must produce rules that are expressible in policy SQL, and the code
layout must funnel all data access through one database entry point.
score, no normalised rating, and no derived vendor win rate. Scores are raw and per-document.
A win rate is a global rating in different clothes and leaks competitive outcomes across the
two-party boundary the product is sold on.
Decisions so far
Where does the transmission system get built? — Greenfield, server-authoritative. A new deployable for the retailer side and the transmission network. HelmDocs integrates later across an API/MCP boundary.
Is this the retailer's system of record, or a layer on their existing stack? — Distribution and evaluation layer. The retailer keeps their existing system of record. This broadcasts the RFP, controls who sees it, collects and ranks bids, and hands the outcome back.
Must a vendor author their bid in our tool, or can they submit from anywhere? — Submit from anywhere; structure on ingest. Accept PDF/DOCX from any vendor and extract the response into the common schema so it is rankable. Native authoring is offered as the better path, never required.
Day one, a retailer posts an RFP. Where does the bidder pool come from? — The retailer brings their own vendor roster. They import it and invite them. Visibility tiers operate over a retailer-scoped list, not a public market. v1 ships no open discovery.
What do bidders actually pay for? — Tools, never access. Seeing RFPs and submitting a bid is free forever. Vendors pay a flat annual subscription for the workspace that helps them win: AI matching, drafting, the reusable compliance vault, status tracking, coordination.
Research: Ramp's product, positioning, and business model — Product. Corporate cards, expense management, AP/bill pay, procurement, travel, treasury (>$1B AUM), Ramp Intelligence (a cross-product AI layer, not a SKU), a developer API, and an official MCP server for agent access to spend data.
Research: Ramp's online sentiment and the critical case — Sentiment is bimodal, not rosy. The core card + expense product is genuinely well-liked across G2 (~4.8), Capterra (4.9), TrustRadius (~9.1) and — more tellingly — on Hacker News, where commentary is unsolicited. Note Trustpilot sits at 3.5, and unsolicited channels skew more critical than vendor-solicited ones.
Define the tenancy, identity, and authorization model — Roster entries (retailer-scoped, unclaimed) vs. claimed global vendor orgs; one org holds issuer and/or vendor capabilities; isolation enforced by Postgres RLS with actor context in transaction-scoped session GUCs.
Research: the RFP / sourcing / procurement ecosystem, both sides — It is not novel. It is the incumbent's model.
Collect real retail RFPs and vendor responses — Scoring scales are per-document and must never be normalised; price is a rate function, not a scalar; response binding is a reliability gradient from tabbed restatement down to free prose. No completed vendor response could be obtained.
Design the visibility and qualification model — Eligibility (durable, per retailer/vendor/category) is separate from audience (per-RFP, ad-hoc or a rule-defined group materialised at issue); audience is fixed at issue while access widens by stage; invited vendors see a teaser until they pass an optional acknowledgement gate.
Design the vendor profile and cross-retailer document vault — Vendor attests, we validate only machine-checkable structure, retailer acceptances form a reusable graph shown as an anonymised count with a k-floor (vendor may opt in to name); we curate a small typed catalogue with ad-hoc needs as per-RFP attachments; we compute expiry state, retailers configure its consequence.
Specify the entitlement and subscription model — The paid feature is persistence, not access: free vendors bid fully but nothing carries across RFPs, and on lapse the vault stays readable while the reuse machinery stops. Entitlement is resolved once into actor context and asserted per feature, never expressed as row visibility — it gates vendor convenience, never retailer-facing correctness. No retailer sponsorship in v1; the payer column stays.
Define the category taxonomy — A curated 50-150 leaf global spine (two-level, UNSPSC-annotated) with retailer-local categories mapped onto it; eligibility and group rules run on the local category and never inherit, while matching and the acceptance graph run on the spine; nodes are immutable with temporal validity and supersession edges, so the historical view is derived rather than versioned.
Obtain real completed vendor responses, including a compliance matrix — Closed unresolved: none exist in public form and retrieval was exhausted. v1 reframed as a demoable POC; the schema is designed against a synthesized response corpus, with the resulting under-constraint recorded as an accepted risk.
Define the common schema for RFPs and responses — Requirement is the atom with criteria as a weighted overlay mapping to requirement sets; the document stays legally authoritative while the retailer-confirmed extraction drives evaluation, every requirement carrying a provenance span; coverage is three-state with the vendor attesting to their own indeterminates at submit; price is a typed rate function evaluated against a scenario published with the RFP. Scores stay raw and per-document, never normalised.
Skeptical review pass over all closed decisions Skeptical review pass over all closed decisions — Two contradictions found and
ticketed (taxonomy supersession vs. eligibility exactness; the free vendor with no vault), plus
constraints added to tenancy, the schema, the vault, data release, extraction confirmation, and
the document catalogue. Nothing previously decided was reversed.
Resolve what eligibility references, and whether local categories are mutable — Eligibility references the retailer-local category only, never the spine, so taxonomy churn can never reach an access decision; local categories get the spine's model (immutable identity, editable label, retire not delete); a split propagates grants to every successor and a merge only to the intersection, as explicit event-stamped writes that never widen and never silently revoke; a deprecated spine leaf auto-resolves when unambiguous and raises a remap task when not.
Specify the vendor pricing and tier structure — No free tier: a hard $100/mo gate on
seeing anything, then Pro $350 and Enterprise $1,000, scaling on seats and AI capability and never
on volume or per-retailer. The rail (vault, cross-retailer reuse, acceptance graph) is in the base
tier and the AI is the upsell; on lapse live participations run to completion, and eligibility
never depends on billing. Supersedes #6 and decision 1 of #20.
Design the app framework - retailer and vendor surfaces - One app with a combined
org x capability picker and fully capability-scoped nav; the vendor bids against a requirement-led
surface that upload imports into, while the retailer's own document stays authoritative with the
extracted overlay confirmed beside it; the invitation wall shows only issuer, category and
deadline; the vendor home pairs in-flight work with eligibility health phrased as consequence;
evaluation is a requirement matrix with ranking left as a view over it. Prototype:
surface map.
Specify the two-party data release rules - Release is a versioned
disclosure-class x release-level matrix, the level computed by one function over audience,
stage, participation and acknowledgement; bids stay sealed until the deadline and then open for
the whole field at once, so bid shopping is structurally impossible; a vendor learns nothing of
the field and everything about their own response including the retailer's scoring of it; exact
volumes sit behind the acknowledgement gate, which stays optional but warns hard; export mirrors
read exactly and is logged. Release never contracts, and win rate exists as no class at all.
Design AI bid ranking, scoring, and its defensibility - The model proposes and
cites, a human commits, and only a commit creates a score of record; routing is by requirement
kind so gates and all price arithmetic are code the model may locate in but never decide; no
ordering exists before commits, only deterministic triage, because a provisional rank anchors the
evaluator invisibly; the criterion-to-requirement mapping is frozen at issue and amending it voids
affected scores. Suggestions carry a model version, committed scores are never re-derived.
Design the extraction confirmation step on both sides - Issue is gated on
confirming mandatory gates and criterion-mapped requirements individually while informational ones
bulk-accept, with the ledger ordered by uncertainty rather than document order; the completeness
sweep blocks only where a second independent pass asserts an obligation nothing covers, and bare
uncovered text is a report; corrections classify on whether a bidder would answer differently,
substantive ones extending the deadline and reopening just the affected answer; vendors resolve
indeterminates and acknowledge gate failures for free, with answer sufficiency as the Pro feature.
Assemble the v1 code layout - The destination, reached. Committed as
da74362,46 files: the domain model as nine schema files with RLS policies beside their tables, the single
database entry point, the scheduler entry, the entitlement assertion helper, and the three
enumerated privileged paths. A seam is real types plus a throwing stub naming its ticket, so a
ticket lands by replacing a body. Drizzle owns what it can diff; functions and the versioned
matrix are hand-authored. Found on the way: a policy/function ordering cycle resolved with
plpgsql, at the cost of unvalidated function bodies until first call.
Build the synthesized response corpus - Closed demo-sufficient. One archived
solicitation with hand-authored ground truth, three synthesized responses spanning the binding
gradient, three distinct rate structures, and two validators asserting the stress-case checklist.
The lowest bid is disqualified and the highest is strongest on the buyer's own criterion, so a
ranking that cannot represent absence gets the field backwards. It produced two schema changes -
nested criteria and a gate no document can satisfy - both applied.
Define the v1 component system and visual identity — extends the existing HelmDocs system rather than authoring one. The amber/orange collision resolves as a boundary, not a repaint: the accent is chrome-only and never enters a data surface, and warning never encodes alone — all four semantic tokens promote unchanged. The encoding axis is settled-vs-unsettled, not good-vs-bad: verdicts carry hue, and anything the artifact cannot settle is achromatic and dashed. Density drawn on the evaluation matrix first — gates pinned above the scored body, roll-up parents uncommittable, totals never rounded, a disqualified column struck but visible. Capability is stated in words (
Acting as {Org} — {Capability}), never in hue, because it is an authorization boundary. Build list: four components and one promotion PR.Build the clickable POC prototype for the internal demo — the walk is issue → seal → switch → bid → open → evaluate → amend, crossing the capability boundary mid-solicitation because one side alone just looks like a procurement tool. The three behaviour-only decisions are all in it: the sealed field opening at once, the dashed proposal that becomes a score only on a human commit, and the amendment that voids rather than recomputes. Building it found two design bugs a static page hid — roll-ups blending committed with proposed, and voiding not reaching the totals — both now constraints on
<EvaluationMatrix>. Throwaway by design; the layout insrc/remains the destination.Define the v1 rate structure types — built, not just decided:
src/pricing/with corpus assertions behindnpm run check:pricing. A rate structure is a composition — one base schedule (flat_unit,volume_bandedwith an explicit whole-basket/marginal flag) plus closed modifiers — evaluated against a closed scenario vocabulary published with the RFP. Three outcomes, not two: priced,shape_unsupported, andinput_missing, which is recoverable by asking the vendor. Abstention is null with a reason, never zero, never the vendor's stated figure, and never a partial sum. The derivation is stored as the record with a versioned evaluator; a scenario amendment voids rather than recomputes. Exceptions carry a closed consequence union because one of the three in the corpus has no price at all.Not yet specified
author direction during the pricing session — weighed and explicitly deferred, not undecided by
accident. It carries the live risks of the hard gate: a retailer's first sourcing event needs
every invited supplier to convert before a full field of bids arrives, and suppliers who cannot
clear $1,200/yr drop out of the retailer's diversity pool. Retailer sponsorship moved here from
Out of scope — it is no longer merely a growth lever but the only proposed answer to that
second problem.
exercises the OCR ladder, and no real tabbed-restatement response was ever obtained. SLC Airport
(142pp, tabbed A-M, shortlist-for-interviews, percentage-rent pricing of the form
MAX(1/12 x guarantee, % of receipts)) would cover all four gaps and is the obvious second set.Belongs to whoever opens the extraction seam rather than to the demo, and is not sharp enough to
ticket until that boundary is drawn.
publishes no per-criterion scale at all because a committee judges it - while the scoring model
resolved here has a single
committed_byper criterion. Multiple scorers per criterion, whethertheir scores average or must be reconciled, and who breaks a deadlock, are all unspecified.
Surfaced by the ranking decision; not sharp enough to ticket until the evaluation surface has
been used against the synthesized corpus.
common schema exists, since matching operates over it.
lifecycle event. Waits on the lifecycle being pinned down.
a later enhancement, but it cannot be specified until the integration boundary is drawn.
ticket, and out of the way until retail v1 is specified.
Out of scope
third-party venue and whether vendors will actually pay. Ruled out by the destination: this map
produces a build spec and assumes demand. If the answer is no, the whole map is moot — that is a
known and accepted risk of this destination.
subscribe. Ruled out for v1 by the cold-start decision.
reporting, contract lifecycle management. Ruled out by the distribution-layer decision — these are
exactly the costs that scale with retailer usage and would eventually break the free promise.
ticket covers the interface without building the product.
considered as this map's destination and declined. Run it as a separate effort. Its naming
question is now answered and no longer waits on the pitch: this is a HelmDocs product, decided
in the Notes above.