Run Roadmap Ledger from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page ranks the six publication postures, runs every item through the exclusion gate, caps every date with a phase, finds silent removals and unclosed shipped items and ranks the feedback surface, then hands it all to the run as facts. The run writes the roadmap policy or the slip notices; the page then holds it to its own facts (rlreply.js).
Base URL and the response shape
Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }
The token is minted for this app (the guest call takes {"slug":"roadmap-ledger"}), so no slug header is needed afterwards. Send it as Authorization: Bearer YOUR_TOKEN. The run body is the input object, sent bare.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or a required key (task, company, items, facts) is missing. The page always sends all five of task, company, items, carry, facts; carry is an empty string for policy. |
| 401 | unauthorized | No token, or a stale one. Mint a guest token or sign in again. |
| 402 | insufficient_credits | The balance is under min_credits. Price with /estimate first. |
| 403 | forbidden | A guest token tried to run: both lanes are metered and need a personal sign-in. |
| 404 | not_found | Unknown job id. |
| 429 | rate_limited | Back off and retry. |
| 5xx | server_error | Transient. Retry with the same Idempotency-Key so a retry never double-bills. |
The task field comes first
Two lanes, chosen by task, in the order the work runs - before anything goes public, then when an item slips:
policy- the roadmap policy: the posture, what each state promises, what stays off the page, the disclaimer, contract clause and citation rules, the feedback surface and the owner.slips- the slip notices: every slipped, descoped, cancelled or vanished item, in the page's order, with a status, a reason, a workaround, who to notify and whether a contract customer hears first.
A missing or unknown task is answered as policy. Both lanes send the same five fields. The facts are the page's checks, one row per line, and a line starting "must:" is a rule the reply has to hold. The page's handoff carries the policy's decisions into carry for the slips lane.
The input
| Field | Required | What |
|---|---|---|
task | yes | policy or slips. If it is missing or neither, do policy and set "task": "policy". |
company | yes | the company, the trigger, buyers, listing, regulation, release machinery, the slip rate, contracts and clauses, the citation rule, feedback today, the owner and hours, cadence, deadline and effort; "not given" means not given. |
items | yes | every roadmap item with its phase, date, category, status, changelog link, reviews since touched, who was told, and the page's flags. |
carry | send it (empty for policy) | slips task only: the policy's decisions, when the policy task ran first; may be empty. |
facts | yes | the page's checks, one line each as area | id | status | evidence, the verdict and the ranked postures. A line starting "must:" applies to your reply. |
retry_note | no | optional; if present, your previous reply was unusable - follow it. |
The rules the model applies
From the source skill, @samber/public-roadmap-strategy - the only standard the run applies:
- A public roadmap is a promise ledger: customers, prospects, competitors and your own sales team hold you to every item on it. No independent study links public roadmaps to trust or sales - argue on stated trade-offs, never a promised return. Mind the Osborne effect.
- Postures: a horizon roadmap is the default (undated buckets or release phases - an order, not a date); a direction page when nobody can commit to item-level upkeep, with a last-reviewed date and a named maintainer; no public roadmap is a real rung; customer briefings under NDA follow the same sales rules; an interactive board only with a triage owner; dated quarters only when release phases exist, few committed items slipped, buyers plan around your dates, and finance reviewed revenue-recognition exposure. Every public posture is deleted, not demoted, while sales promise unbuilt features and leadership will not sign the citation rule - fix sales governance first.
- Vocabulary, defined on the page: horizon buckets defined by engineering state, release phases (exploring, in design, private preview, public preview, GA), or quarters only with a phase attached. Cap every date with a phase; date only what has entered private preview. Publish a visible not planned or cancelled state with a reason. Label community proposals apart from vendor plans. Write items problem-first.
- The exclusion gate - never public: security fixes and hardening, unannounced deprecations and breaking changes, pricing, packaging and anything that would cannibalize a current plan, partner and third-party names and M&A before announcement, competitive bets and surprise launches, customer-specific commitments, internal plumbing.
- Guardrails: a page disclaimer (informational, not a commitment, subject to change, not to be relied on for purchasing); forward-looking-statement language for listed companies; an MSA clause that purchases are not contingent on future functionality, with a self-serve equivalent. A feature written into an order form is binding whatever the disclaimer says - inventory every such contract. Citation rules signed by the sales leader and the CEO: cite only the public roadmap at its printed commitment level, deeper previews only under NDA with product present, no dates anywhere the page does not show them, legal and finance approve any roadmap item in an order form, deals that depend on unbuilt features go to product.
- Feedback: locked items with debate in one channel is the default; reactions on items; a separate voting board only with a named triage owner. Votes are one weighted signal, never the ranking. Freeze a board nobody triages, read-only behind a dated notice, and plan the exit before the launch.
- Lifecycle: one named owner and a backup, a review cadence that touches every item, stable IDs with subscriptions, every shipped item closed with a changelog link, a snapshot at each review. A stale public roadmap is worse than none.
- Slips: change the status on the item - delayed, descoped or not planned - never delete it; a plain-language reason; a workaround; notify subscribers, reactors and every account the briefing log shows; contract customers hear privately from the account owner days before the public update, after legal checks the order form; keep cancelled items visible at least one review cycle.
House rules
- Use only the input. Never invent an item, a customer, a person, a date or a vendor fact.
- The page's checks, rankings and flags are settled: never move a posture, rank differently or call a failed gate fine.
- No URL at all.
- Write in the language of the input; keep the JSON keys, posture, family and surface ids and item names as given.
- Never use backticks, never write the input's field names as labels in your text, and never copy this prompt's wording into a reply: the rules above are the substance you apply, so restate each one in this company's own terms - who does what, in which channel, about which of its items and facts - rather than echoing the rule.
- Wording with legal weight - the disclaimer, the contract clause, a notice to a contract customer - is a draft for counsel: say so once, in your own words, where it matters for this company.
- Reply with ONLY one JSON object - no prose before or after, no code fences.
The replies
policy
Write the roadmap policy - the posture, what each state promises, what never goes public, the guardrails, the feedback surface and who keeps it current.
summary- one sentence, at most 40 words: the posture, and what must happen before anything is published.posture-recommendedandalternative(horizon,direction,none,briefings,boardordated, exactly as the facts rank them),bundle(the bundled posture the facts name, else empty),why(one sentence naming the reasons the facts give for this ranking).vocabulary-family(buckets,phases,quarters,themesornone, exactly as the facts read it),states(objects:state,meaning- what a customer may rely on; list the family's own states - each release phase, each bucket or each quarter-with-phase the items use - and include a not planned state; when the items include a community proposal, add a state that labels proposals apart from vendor plans; quarters always carry a phase;[]when the family is none).gate- one object per item the facts keep off the page, in their order:item(as written),reason(one sentence).guardrails-disclaimer(one sentence in this company's voice, as it would sit on its page; the standard phrases not a commitment and subject to change are expected here),clause(one sentence: the contract clause and the self-serve equivalent),citation(short strings: the citation rules, each written for this company - name who signs and enforces it, the channels its reps use, and its own items or contracts where they apply),forward_looking(one sentence for a listed company, else empty).feedback-surface(locked,reactions,boardorfrozen, exactly as the facts read it),why(one sentence).lifecycle-owner(the owner as given, else "unassigned - name one"),cadence(one sentence),close(one sentence: shipped items closed with a changelog link).caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "policy",
"summary": "one sentence",
"posture": { "recommended": "horizon", "alternative": "direction", "bundle": "", "why": "one sentence" },
"vocabulary": { "family": "phases", "states": [ { "state": "Private preview", "meaning": "one sentence" } ] },
"gate": [ { "item": "as written", "reason": "one sentence" } ],
"guardrails": { "disclaimer": "one sentence", "clause": "one sentence", "citation": [ "a short string" ], "forward_looking": "" },
"feedback": { "surface": "locked", "why": "one sentence" },
"lifecycle": { "owner": "a name", "cadence": "one sentence", "close": "one sentence" },
"caveats": [ ]
}
The page holds it to its own facts (check() in rlreply.js; any warning makes the reply flagged): the reply answers the task it was sent; no URL; no backticks; a summary of at most 40 words; the page's posture, alternative and bundle; its vocabulary family, with at least three states including a not planned state (and every quarter capped by a phase) unless the family is none; exactly the gated items in the page's order, each with a reason; a disclaimer saying not a commitment and subject to change; the contract clause; at least three citation rules; forward-looking-statement language for a listed company; the page's feedback surface; the owner as given, or a call to name one; a cadence; shipped items closed with a changelog link; at most four caveats.
slips
Write the notice for every slipped, descoped, cancelled or silently removed item.
summary- one sentence, at most 40 words.slips- one object per slipped item, in the facts' order:item(as written),status(delayed,descopedornot planned- exactly as the facts read it; a removed item is restored with one of the three - delayed when the input suggests the work is still intended, not planned when it shows the item was dropped; when it shows neither, restore it as delayed and tell readers when its future will be settled, using the company's own review cadence),reason(one or two sentences, plain language),workaround(one sentence: an alternative the input names, or else what to do meanwhile in plain terms - keep the current approach, subscribe for the next update, talk to the account owner; never invent a product or feature),notify(short strings: subscribers, reactors only when the feedback surface is reactions, the accounts the briefing log shows - each item's customers-told value is that log: deck or NDA audiences are briefed accounts - and, for an item a contract names, the contract customers through their account owner),private_first(true when a contract names the item - the account owner tells that customer days before the public update).caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "slips",
"summary": "one sentence",
"slips": [ { "item": "as written", "status": "delayed", "reason": "one or two sentences", "workaround": "one sentence", "notify": [ "subscribers" ], "private_first": false } ],
"caveats": [ ]
}
The page requires: the reply answers the task it was sent; the same text rules; exactly the page's slipped items in its order; each status as the page reads it (a vanished item restored as delayed, descoped or not planned); a reason and a workaround each; subscribers notified; private first exactly when a contract names the item; and no item ever deleted.
Worked examples
Three runs from the page's free samples (invented companies): the policy for a webhook API whose sales team promises unbuilt features, the slip notices for the same company carrying that policy, and the policy for a listed platform ready for dated quarters. Each input is exactly what the page sends; each reply is the saved reply the sample replays, shipped unedited - but the facts and long strings are shortened in this display only, and every cut is listed under the example. The last line is the page's own check of the full reply.
policy - A webhook API whose sales team promises
The page read it as: fix sales governance before any public page; postures none, briefings; vocabulary none; feedback frozen; 0 of 5 gates; failing checks: citation, contracts, revrec, exclusion, dates, visible, lifecycle, board.
Input (facts is 2770 characters; the first 1395 are shown):
{
"task": "policy",
"company": "Company: Hookwend\nThe product and why a roadmap now: A webhook delivery API; the CEO wants a public roadmap because reps keep promising features from the internal one.\nTrigger: Sales keeps promising unbuilt features; benchmark named: not given; buyers: Both; listed: no; regulated customers: no; open-core with public issues: no\nRelease phases exist: no; changelog: yes; committed items slipped last year: 40%; buyers plan around dates: no; finance reviewed revenue recognition: no\nMSA not-contingent clause: no; self-serve equivalent: no; contracts naming roadmap items: 2; leadership signs the citation rule: no\nFeedback today: A board nobody triages; owner: not given; owner hours a month: 2; review cadence: None; deadline: no; a standing practice; effort ceiling: low; as-of: 2026-11-01",
"items": "Items in the order given (item | phase | date | category | status | changelog | reviews since touched | told):\n- Replay failed deliveries from the dashboard | A horizon bucket (Next) | Q1 | a customer-facing capability | planned | changelog no | 3 reviews since touched | customers told: deck | BARE DATE\n- Webhook signing v2 | A horizon bucket (Now) | undated | a customer-facing capability | delayed | changelog no | 1 review since touched | customers told: contract | SLIP - contract customers hear privately first | solution-named\n- Retry webhooks for 7 days | A horizon bucket (Later) | undated | a customer-facing capability | removed without a state | changelog no | 4 reviews since touched | customers told: deck | SLIP\n- Patch the signature timing leak | A horizon bucket (Now) | undated | a security fix or hardening | planned | changelog no | 0 reviews since touched | customers told: none | OFF THE PAGE",
"carry": "",
"facts": "The page's checks (8 fail, 4 warn, 0 pass, 2 n/a); verdict: fix sales governance before any public page:\n- Posture and guardrails | posture | na | No public roadmap first, customer briefings under NDA as the alternative - sales promise unbuilt features and leadership will not sign the citation rule - every public posture is deleted until the rule is signed; an enterprise audience moves customer briefings up the value ranking\n- Posture and guardrails | citation | fail | sales promise unbuilt features and nobody signs the rule - publishing hands reps a larger catalogue to promise from\n- Posture and guardrails | contracts | fail | 2 order forms or side letters name roadmap items - binding whatever the disclaimer says; counsel and finance review each, the account owner calls the customer\n- Posture and guardrails | clause | warn | no clause - counsel decides whether to add it to new and renewing contracts\n- Posture and guardrails | selfserve | warn | an MSA clause does not cover click-through accounts\n- Posture and guardrails | revrec | fail | dated, named features can create a specified-upgrade obligation - bring finance in before any date reaches a customer\n- The items | exclusion | fail | Patch the signature timing leak (a security fix or hardening)\n- The items | dates | fail | Replay failed deliveries from the dashboard (Q1, a horizon bucket) - a bare quarter reads as GA to\n\n[... 1375 more characters cut from this display ...]"
}
Reply:
{
"task": "policy",
"summary": "Publish no public roadmap for now and brief customers under NDA instead; before any page goes live, the sales leader and the CEO must sign the citation rule and counsel must review the 2 contracts naming roadmap items.",
"posture": {
"recommended": "none",
"alternative": "briefings",
"bundle": "",
"why": "Reps already promise unbuilt features and leadership will not sign the citation rule, so every public posture is deleted until sales governance is fixed, leaving no public roadmap first and NDA customer briefings as the alternative."
},
"vocabulary": {
"family": "none",
"states": []
},
"gate": [
{
"item": "Patch the signature timing leak",
"reason": "It is a security fix, and naming it before it ships tells attackers where the weakness is."
}
],
"guardrails": {
"disclaimer": "Anything shared about future plans is informational only, is not a commitment, is subject to change and must not be relied on for purchasing decisions.",
"clause": "Counsel should decide whether new and renewing contracts state that purchases are not contingent on future functionality, with the same statement in the click-through terms, since Hookwend has neither today.",
"citation": [
"No rep cites a roadmap item until the sales leader and the CEO sign this rule.",
"Previews of future work happen only in NDA briefings with product present.",
"No dates for any item in decks, emails or calls.",
"Legal and finance approve any roadmap item before it enters an order form.",
"Any deal that depends on an unbuilt feature goes to product, not to the rep."
],
"forward_looking": ""
},
"feedback": {
"surface": "frozen",
"why": "Nobody triages the current board, so freeze it read-only behind a dated notice that points to one feedback channel, and plan its exit before the next launch."
},
"lifecycle": {
"owner": "unassigned - name one",
"cadence": "There is no review cadence today; the named owner and a backup should review every item, including the briefing deck, at a fixed interval and snapshot it each time.",
"close": "Every shipped item is closed with a changelog link, which the existing changelog makes possible."
},
"caveats": [
"Two order forms or side letters name roadmap items and bind Hookwend whatever any disclaimer says; counsel and finance review each and the account owner calls the customer.",
"Committed items slipped 40% of the time last year, so even briefings should carry no dates; Replay failed deliveries from the dashboard already shows a bare Q1.",
"Retry webhooks for 7 days vanished from the deck; restore it as delayed, descoped or not planned rather than leave it gone.",
"Finance has not reviewed revenue-recognition exposure; bring them in before any date reaches a customer."
]
}
What step 5 prints for it: none: Publish no public roadmap for now and brief customers under NDA instead; before any page goes live, the sales leader and the CEO must sign the citation rule and counsel must review the 2 contracts naming roadmap items.
The page's check: kept - nothing to flag
slips - A webhook API whose sales team promises
The input carries the saved policy's decisions (R.laneForm fills carry from the policy reply), as the page's handoff does. The slipped items in order: wire, wireShown, wireNote, reply, shown, cuts, status, notes, carried, gist.
Input (facts is 2778 characters; the first 1395 are shown):
{
"task": "slips",
"company": "Company: Hookwend\nThe product and why a roadmap now: A webhook delivery API; the CEO wants a public roadmap because reps keep promising features from the internal one.\nTrigger: Sales keeps promising unbuilt features; benchmark named: not given; buyers: Both; listed: no; regulated customers: no; open-core with public issues: no\nRelease phases exist: no; changelog: yes; committed items slipped last year: 40%; buyers plan around dates: no; finance reviewed revenue recognition: no\nMSA not-contingent clause: no; self-serve equivalent: no; contracts naming roadmap items: 2; leadership signs the citation rule: no\nFeedback today: A board nobody triages; owner: not given; owner hours a month: 2; review cadence: None; deadline: no; a standing practice; effort ceiling: low; as-of: 2026-11-01",
"items": "Items in the order given (item | phase | date | category | status | changelog | reviews since touched | told):\n- Replay failed deliveries from the dashboard | A horizon bucket (Next) | Q1 | a customer-facing capability | planned | changelog no | 3 reviews since touched | customers told: deck | BARE DATE\n- Webhook signing v2 | A horizon bucket (Now) | undated | a customer-facing capability | delayed | changelog no | 1 review since touched | customers told: contract | SLIP - contract customers hear privately first | solution-named\n- Retry webhooks for 7 days | A horizon bucket (Later) | undated | a customer-facing capability | removed without a state | changelog no | 4 reviews since touched | customers told: deck | SLIP\n- Patch the signature timing leak | A horizon bucket (Now) | undated | a security fix or hardening | planned | changelog no | 0 reviews since touched | customers told: none | OFF THE PAGE",
"carry": "Posture: none\nVocabulary: none (no public states)\nFeedback: frozen\nOwner: unassigned - name one; cadence: There is no review cadence today; the named owner and a backup should review every item, including the briefing deck, at a fixed interval and snapshot it each time.\nCitation: No rep cites a roadmap item until the sales leader and the CEO sign this rule. | Previews of future work happen only in NDA briefings with product present. | No dates for any item in decks, emails or calls. | Legal and finance approve any roadmap item before it enters an order form. | Any deal that depends on an unbuilt feature goes to product, not to the rep.",
"facts": "The page's checks (8 fail, 4 warn, 0 pass, 2 n/a); verdict: fix sales governance before any public page:\n- Posture and guardrails | posture | na | No public roadmap first, customer briefings under NDA as the alternative - sales promise unbuilt features and leadership will not sign the citation rule - every public posture is deleted until the rule is signed; an enterprise audience moves customer briefings up the value ranking\n- Posture and guardrails | citation | fail | sales promise unbuilt features and nobody signs the rule - publishing hands reps a larger catalogue to promise from\n- Posture and guardrails | contracts | fail | 2 order forms or side letters name roadmap items - binding whatever the disclaimer says; counsel and finance review each, the account owner calls the customer\n- Posture and guardrails | clause | warn | no clause - counsel decides whether to add it to new and renewing contracts\n- Posture and guardrails | selfserve | warn | an MSA clause does not cover click-through accounts\n- Posture and guardrails | revrec | fail | dated, named features can create a specified-upgrade obligation - bring finance in before any date reaches a customer\n- The items | exclusion | fail | Patch the signature timing leak (a security fix or hardening)\n- The items | dates | fail | Replay failed deliveries from the dashboard (Q1, a horizon bucket) - a bare quarter reads as GA to\n\n[... 1383 more characters cut from this display ...]"
}
Reply:
{
"task": "slips",
"summary": "Two notices: Webhook signing v2 is delayed and its contract customers hear privately first; Retry webhooks for 7 days, which vanished without a state, is restored as delayed and stays visible.",
"slips": [
{
"item": "Webhook signing v2",
"status": "delayed",
"reason": "This work is taking longer than planned and will not land in the current window. It stays on the list as delayed, and we will share a new phase once one is firm.",
"workaround": "Keep verifying deliveries with the signing you use today; nothing changes for existing integrations while this is delayed.",
"notify": [
"subscribers",
"contract customers who have it in their order form",
"the account owner for each of those contract customers"
],
"private_first": true
},
{
"item": "Retry webhooks for 7 days",
"status": "delayed",
"reason": "This item disappeared from the list without any explanation, which was a mistake. It is now restored with a visible delayed state: it has not been worked on recently and has no new timing yet.",
"workaround": "Meanwhile, keep your own handling for deliveries that fail, and subscribe to this item to hear when its state changes.",
"notify": [
"subscribers",
"accounts shown the briefing deck"
],
"private_first": false
}
],
"caveats": [
"There is no public page yet, so these states go into the briefing deck and direct notices, never a new public roadmap.",
"Counsel and finance review both order forms before the account owner calls; the public update waits days after that call.",
"No owner is named yet - name one and a backup before these notices go out.",
"This is not legal advice; route the final wording to counsel."
]
}
What step 5 prints for it: Webhook signing v2 delayed | Retry webhooks for 7 days delayed
The page's check: kept - nothing to flag
policy - A listed platform ready for dated quarters
The page read it as: a roadmap you can publish and keep; postures dated, horizon, direction, briefings, none, board (bundled with briefings); vocabulary quarters; feedback locked; 5 of 5 gates; failing checks: none.
Input (facts is 1932 characters; the first 1397 are shown):
{
"task": "policy",
"company": "Company: Beaconmere\nThe product and why a roadmap now: A listed observability platform; enterprise buyers plan migrations around the roadmap and procurement asks for it.\nTrigger: Customers and prospects keep asking what's coming; benchmark named: like Stripe; buyers: Mostly enterprise buyers under negotiated contracts; listed: yes; regulated customers: yes; open-core with public issues: no\nRelease phases exist: yes; changelog: yes; committed items slipped last year: 8%; buyers plan around dates: yes; finance reviewed revenue recognition: yes\nMSA not-contingent clause: yes; self-serve equivalent: yes; contracts naming roadmap items: not given; leadership signs the citation rule: yes\nFeedback today: No board; owner: Ruth Achebe; owner hours a month: 20; review cadence: Monthly; deadline: no; a standing practice; effort ceiling: high; as-of: 2026-11-01",
"items": "Items in the order given (item | phase | date | category | status | changelog | reviews since touched | told):\n- Trace sampling you can set per service | Private preview | Q1 2027 | a customer-facing capability | planned | changelog no | 0 reviews since touched | customers told: none\n- Alert on budget burn, not raw errors | Public preview | Q2 2027 | a customer-facing capability | planned | changelog no | 0 reviews since touched | customers told: none\n- Logs retained 13 months | GA | undated | a customer-facing capability | shipped | changelog yes | 0 reviews since touched | customers told: none\n- Dashboards as code | Exploring | undated | a customer-facing capability | not planned | changelog no | 0 reviews since touched | customers told: deck | SLIP",
"carry": "",
"facts": "The page's checks (0 fail, 1 warn, 6 pass, 2 n/a); verdict: a roadmap you can publish and keep:\n- Posture and guardrails | posture | na | Dated quarters first, horizon roadmap as the alternative, bundled with customer briefings under NDA - phases exist, the slip rate is low, buyers plan around dates and finance signed off - dated quarters, capped by phase; regulated enterprise buyers - bundle NDA customer briefings with the public page; an enterprise audience moves customer briefings up the value ranking\n- Posture and guardrails | citation | pass | the sales leader and the CEO sign and enforce it\n- Posture and guardrails | safeharbor | warn | add securities safe-harbor language and run roadmap statements through disclosure review\n- Posture and guardrails | revrec | pass | finance reviewed revenue-recognition exposure\n- The items | exclusion | pass | 4 items, each a customer-facing capability\n- The items | dates | pass | every dated item has entered private preview or later\n- The items | visible | pass | every slip carries a state\n- Lifecycle and measurement | lifecycle | pass | owner Ruth Achebe; monthly reviews; 1 of 1 shipped item closed with a changelog link\n- Lifecycle and measurement | measures | na | freshness 100%; silent removals 0; close rate 100%; slip rate 25% of committed items\nPostures by the skill's ranking, in order: dated, horizon, direction, briefings, none,\n\n[... 535 more characters cut from this display ...]"
}
Reply (long strings shortened for display - see the cuts below):
{
"task": "policy",
"summary": "Publish a dated-quarters roadmap with every quarter capped by its release phase, bundled with NDA customer briefings; before it goes live, add safe-harbor language and run the page through disclosure review.",
"posture": {
"recommended": "dated",
"alternative": "horizon",
"bundle": "briefings",
"why": "Beaconmere has release phases, slipped only 8% of committed items last year, sells to enterprise buyers who plan migrations around dates and has finance sign-off on revenue recognition, so publish dated quarters capped by phase, with a horizon [... 67 more characters cut from this display]"
},
"vocabulary": {
"family": "quarters",
"states": [
{
"state": "Exploring, undated",
"meaning": "We are studying the problem; it carries no quarter and no promise that it will be built."
},
{
"state": "In design, undated",
"meaning": "We are shaping a solution; scope can still change and no quarter is shown until it reaches private preview."
},
{
"state": "Private preview, target quarter",
"meaning": "Invited customers can try it now; the quarter is our target for the next phase, not a GA date, and it may move."
},
{
"state": "Public preview, target quarter",
"meaning": "Any customer can use it under preview terms; the quarter is our target for GA, and the interface may still change before then."
},
{
"state": "GA",
"meaning": "Generally available and fully supported, closed out with a changelog entry."
},
{
"state": "Not planned",
"meaning": "We have decided not to build this; the item stays visible with its reason for at least one review cycle."
}
]
},
"gate": [],
"guardrails": {
"disclaimer": "This roadmap is informational only, is not a commitment, is subject to change and must not be relied on for purchasing decisions.",
"clause": "Keep the MSA clause that purchases are not contingent on future functionality and its matching statement in the self-serve terms, and have counsel inventory every order form for named roadmap items, which bind Beaconmere whatever the [... 17 more characters cut from this display]",
"citation": [
"Cite only the public roadmap, at the phase and quarter it shows.",
"Deeper previews only in NDA briefings with product present.",
"No dates anywhere the page does not show them.",
"Legal and finance approve any roadmap item before it enters an order form.",
"Any deal that depends on an unbuilt feature goes to product."
],
"forward_looking": "This roadmap contains forward-looking statements about planned products and timing that involve risks and uncertainties, actual results may differ materially, and Beaconmere undertakes no obligation to update them."
},
"feedback": {
"surface": "locked",
"why": "There is no board today, so lock each item and keep debate in one channel rather than launching a voting board nobody is named to triage."
},
"lifecycle": {
"owner": "Ruth Achebe",
"cadence": "Ruth Achebe, with a named backup, reviews every item and every quarter monthly within their 20 hours a month, moves any slipping date the same day, and snapshots the page at each review.",
"close": "Every shipped item is closed with a changelog link, as Logs retained 13 months already is."
},
"caveats": [
"Beaconmere is listed, so add securities safe-harbor language and run every roadmap statement through disclosure review before publishing.",
"Dashboards as code is not planned and was shown in the deck; keep it visible with its reason for at least one review cycle and tell the accounts that saw it.",
"Whether any contract names a roadmap item is not given; inventory the order forms before Trace sampling you can set per service and Alert on budget burn, not raw errors go public with quarters.",
"The page's slip rate is 25% of committed items against 8% last year; if dates keep slipping, step down to the horizon roadmap."
]
}
Cut from this display only (the saved reply and the check use the full text): posture.why (306 characters, 239 shown); guardrails.clause (246 characters, 229 shown).
What step 5 prints for it: dated: Publish a dated-quarters roadmap with every quarter capped by its release phase, bundled with NDA customer briefings; before it goes live, add safe-harbor language and run the page through disclosure review.
The page's check: kept - nothing to flag
Build the input yourself
Load the engine in node (global.window = global; require("./rlkit.js"); require("./rlreply.js")), then:
const K = LedgerKit, R = LedgerReply;
const f = K.form(Object.assign(K.blank(), { name: "Acme", owner: "Ann", hours: "6", phases: "yes",
items: "Faster cold starts | In design | Q1 2027 | feature | planned | no | 0 | none\nClose the SSRF hole | Now | | security | planned | no | 0 | none\nOld export | Later | | feature | removed | no | 2 | deck" }));
// f.rank (the ranked postures, the alternative and any bundle), f.family, f.surface, f.itemRows, f.excluded, f.slipped, f.gate
// K.ledgerMd(f), K.itemsCsv(f) - the public ledger draft and the items
const b = K.buildInput(f, "policy"); // or "slips" (R.laneForm(form, "slips", policy) fills carry)
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "policy");
// R.check(reply, b) // {status: kept or flagged, notes}
Get a token for the steps below from /tokens.html.
Step 1 — a tiny client helper
One function that sends JSON and unwraps the response. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="YOUR_TOKEN" # from https://roadmap-ledger.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}
import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # from https://roadmap-ledger.skillsafe.ai/tokens.html
def api(method, path, body=None, headers=None):
req = urllib.request.Request(BASE + path, method=method,
data=None if body is None else json.dumps(body).encode(),
headers={"Content-Type": "application/json", **({"Authorization": "Bearer " + TOKEN} if TOKEN else {}), **(headers or {})})
with urllib.request.urlopen(req) as r:
env = json.load(r)
if env.get("error"):
raise RuntimeError(env["error"])
return env["data"]
// roadmap-ledger.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "YOUR_TOKEN"; // from https://roadmap-ledger.skillsafe.ai/tokens.html
async function api(method, path, body, headers = {}) {
const r = await fetch(BASE + path, { method, headers: { "Content-Type": "application/json",
...(TOKEN ? { Authorization: "Bearer " + TOKEN } : {}), ...headers },
body: body === undefined ? undefined : JSON.stringify(body) });
const env = await r.json();
if (env.error) throw new Error(env.error.code + ": " + env.error.message);
return env.data;
}
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
var token = "YOUR_TOKEN" // from https://roadmap-ledger.skillsafe.ai/tokens.html
func api(method, path string, body any, headers map[string]string) (map[string]any, error) {
var buf bytes.Buffer
if body != nil { json.NewEncoder(&buf).Encode(body) }
req, _ := http.NewRequest(method, base+path, &buf)
req.Header.Set("Content-Type", "application/json")
if token != "" { req.Header.Set("Authorization", "Bearer "+token) }
for k, v := range headers { req.Header.Set(k, v) }
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
var env struct { Data map[string]any; Error map[string]any }
json.NewDecoder(res.Body).Decode(&env)
if env.Error != nil { return nil, fmt.Errorf("%v", env.Error) }
return env.Data, nil
}
// Roadmap Ledger.java -- Java 17+, with com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.*;
import java.net.URI; import java.net.http.*; import java.util.*;
public class Roadmap Ledger {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "YOUR_TOKEN"; // from https://roadmap-ledger.skillsafe.ai/tokens.html
static final ObjectMapper M = new ObjectMapper();
static final HttpClient C = HttpClient.newHttpClient();
static JsonNode api(String method, String path, Object body, Map<String, String> headers) throws Exception {
var b = HttpRequest.newBuilder(URI.create(BASE + path)).header("Content-Type", "application/json");
if (!token.isEmpty()) b.header("Authorization", "Bearer " + token);
if (headers != null) headers.forEach(b::header);
b.method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(M.writeValueAsString(body)));
JsonNode env = M.readTree(C.send(b.build(), HttpResponse.BodyHandlers.ofString()).body());
if (env.hasNonNull("error")) throw new RuntimeException(env.get("error").toString());
return env.get("data");
}
}
require "json"
require "net/http"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # from https://roadmap-ledger.skillsafe.ai/tokens.html
def api(method, path, body = nil, headers = {})
uri = URI(BASE + path)
req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri,
{ "Content-Type" => "application/json" }.merge(TOKEN.empty? ? {} : { "Authorization" => "Bearer #{TOKEN}" }).merge(headers))
req.body = JSON.generate(body) unless body.nil?
env = JSON.parse(Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }.body)
raise env["error"].to_s if env["error"]
env["data"]
end
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = "YOUR_TOKEN"; // from https://roadmap-ledger.skillsafe.ai/tokens.html
function api(string $method, string $path, $body = null, array $headers = []) {
global $TOKEN;
$h = ["Content-Type: application/json"];
if ($TOKEN) $h[] = "Authorization: Bearer $TOKEN";
foreach ($headers as $k => $v) $h[] = "$k: $v";
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $h, CURLOPT_RETURNTRANSFER => true]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$env = json_decode(curl_exec($ch), true);
if (!empty($env["error"])) throw new Exception(json_encode($env["error"]));
return $env["data"];
}
// Program.cs -- .NET 6+ console app
using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes;
static class Api {
public const string Base = "https://api.skillsafe.ai/v1/app-api";
public static string Token = "YOUR_TOKEN"; // from https://roadmap-ledger.skillsafe.ai/tokens.html
static readonly HttpClient C = new();
public static async Task<JsonNode?> Call(string method, string path, object? body = null, Dictionary<string, string>? headers = null) {
var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
if (Token != "") req.Headers.Add("Authorization", "Bearer " + Token);
if (headers != null) foreach (var h in headers) req.Headers.Add(h.Key, h.Value);
if (body != null) req.Content = JsonContent.Create(body);
var env = JsonNode.Parse(await (await C.SendAsync(req)).Content.ReadAsStringAsync())!;
if (env["error"] is JsonNode e && e.GetValueKind() != JsonValueKind.Null) throw new Exception(e.ToJsonString());
return env["data"];
}
}
Step 2 — a token
A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"roadmap-ledger"}' | jq -r '.data.token')
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = ""
TOKEN = api("POST", "/guest", {"slug": "roadmap-ledger"})["token"]
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = "";
TOKEN = (await api("POST", "/guest", { slug: "roadmap-ledger" })).token;
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = ""
g, err := api("POST", "/guest", map[string]string{"slug": "roadmap-ledger"}, nil)
if err != nil { panic(err) }
token = g["token"].(string)
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = "";
token = api("POST", "/guest", Map.of("slug", "roadmap-ledger"), null).get("token").asText();
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN.replace("")
TOKEN.replace(api("POST", "/guest", { slug: "roadmap-ledger" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "roadmap-ledger"])["token"];
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
Api.Token = "";
Api.Token = (await Api.Call("POST", "/guest", new { slug = "roadmap-ledger" }))!["token"]!.GetValue<string>();
Step 3 — GET /me
Who the token is (subject_type: user or guest) and the credit balance.
api GET /me | jq '.data | {subject_type, credits}'
me = api("GET", "/me")
print(me["subject_type"], me.get("credits"))
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits);
me, _ := api("GET", "/me", nil, nil)
fmt.Println(me["subject_type"], me["credits"])
JsonNode me = api("GET", "/me", null, null);
System.out.println(me.get("subject_type") + " " + me.get("credits"));
me = api("GET", "/me")
puts [me["subject_type"], me["credits"]].inspect
$me = api("GET", "/me");
echo $me["subject_type"], " ", $me["credits"] ?? "", "\n";
var me = await Api.Call("GET", "/me");
Console.WriteLine($"{me!["subject_type"]} {me["credits"]}");
Step 4 — POST /estimate
Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.
# input.json: the run input object - {"task": "policy", "company": "Company: Hookwend
The product and why a roadmap now: ...", "items": "Items in the order given (item | phase | date | category | status | changelog | reviews since touched | told):
- Replay failed deliveries from the dashboard | ...", "carry": "", "facts": "The page's checks (...); verdict: fix sales governance before any public page:
- Posture and guardrails | posture | na | ..."}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}'
inp = json.load(open("input.json")) # {"task": "policy", "company": "Company: Hookwend
The product and why a roadmap now: ...", "items": "Items in the order given (item | phase | date | category | status | changelog | reviews since touched | told):
- Replay failed deliveries from the dashboard | ...", "carry": "", "facts": "The page's checks (...); verdict: fix sales governance before any public page:
- Posture and guardrails | posture | na | ..."}
# - see the input table
est = api("POST", "/estimate", inp)
print(est["hold_credits"], est["min_credits"], est["model"], est.get("warnings"))
import { readFileSync } from "node:fs";
const input = JSON.parse(readFileSync("input.json", "utf8")); // {task: "policy" or "slips", company, items, carry, facts} (see the input table)
const est = await api("POST", "/estimate", input);
console.log(est.hold_credits, est.min_credits, est.model, est.warnings);
raw, _ := os.ReadFile("input.json") // add "os" to the imports
var input map[string]any
json.Unmarshal(raw, &input)
est, _ := api("POST", "/estimate", input, nil)
fmt.Println(est["hold_credits"], est["min_credits"], est["model"])
Map<String, Object> input = M.readValue(new java.io.File("input.json"), Map.class);
JsonNode est = api("POST", "/estimate", input, null);
System.out.println(est.get("hold_credits") + " " + est.get("min_credits") + " " + est.get("model"));
input = JSON.parse(File.read("input.json"))
est = api("POST", "/estimate", input)
puts [est["hold_credits"], est["min_credits"], est["model"]].inspect
$input = json_decode(file_get_contents("input.json"), true);
$est = api("POST", "/estimate", $input);
echo $est["hold_credits"], " ", $est["min_credits"], " ", $est["model"], "\n";
var input = JsonNode.Parse(File.ReadAllText("input.json"))!;
var est = await Api.Call("POST", "/estimate", input);
Console.WriteLine($"{est!["hold_credits"]} {est["min_credits"]} {est["model"]}");
Step 5 — POST /run, then poll the job
Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses roadmap-ledger:<task>:<inputHash>:a<attempt> - the lane is part of the key, retry_note is left out of the hash - and its one automatic reformat retry is a new attempt number, named in the page as one extra run. A policy reply carries summary, posture, vocabulary, gate, guardrails, feedback, lifecycle and caveats; a slips reply carries summary, slips and caveats. The code below prints the gist of either: the posture and the summary, or each slipped item and its status.
KEY="roadmap-ledger:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r 'if .task == "slips" then (.slips[] | "\(.item): \(.status)")
else "\(.posture.recommended): \(.summary)" end' reply.json
import hashlib, time
key = "roadmap-ledger:" + inp["task"] + ":" + hashlib.sha256(json.dumps(inp).encode()).hexdigest()[:16] + ":a1"
job = api("POST", "/run", inp, {"Idempotency-Key": key})
while True:
j = api("GET", "/jobs/" + job["job_id"])
if j["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(2)
reply = json.loads(j["output"]["output"]) # the reply as one JSON object
print(j["charged_credits"], j.get("truncated"), reply["task"])
if reply["task"] == "slips":
for x in reply["slips"]:
print(x["item"], x["status"])
else:
print(reply["posture"]["recommended"], reply["summary"])
import { createHash } from "node:crypto";
const key = `roadmap-ledger:${input.task}:${createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16)}:a1`;
const job = await api("POST", "/run", input, { "Idempotency-Key": key });
let j;
do { await new Promise((r) => setTimeout(r, 2000)); j = await api("GET", "/jobs/" + job.job_id); }
while (!["succeeded", "failed", "cancelled"].includes(j.status));
const reply = JSON.parse(j.output.output);
console.log(j.charged_credits, j.truncated, reply.task);
if (reply.task === "slips") { for (const x of reply.slips) console.log(x.item, x.status); }
else { console.log(reply.posture.recommended, reply.summary); }
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "roadmap-ledger:" + input["task"].(string) + ":" + hex.EncodeToString(sum[:8]) + ":a1"
job, _ := api("POST", "/run", input, map[string]string{"Idempotency-Key": key})
var j map[string]any
for {
time.Sleep(2 * time.Second)
j, _ = api("GET", "/jobs/"+job["job_id"].(string), nil, nil)
if s := j["status"]; s == "succeeded" || s == "failed" || s == "cancelled" { break }
}
var reply map[string]any
json.Unmarshal([]byte(j["output"].(map[string]any)["output"].(string)), &reply)
fmt.Println(j["charged_credits"], j["truncated"], reply["task"])
if reply["task"] == "slips" {
ls, _ := reply["slips"].([]any)
for _, x := range ls {
xx := x.(map[string]any)
fmt.Println(xx["item"], xx["status"])
}
} else {
p, _ := reply["posture"].(map[string]any)
fmt.Println(p["recommended"], reply["summary"])
}
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "roadmap-ledger:" + input.get("task") + ":" + HexFormat.of().formatHex(digest, 0, 8) + ":a1";
JsonNode job = api("POST", "/run", input, Map.of("Idempotency-Key", key)), j;
do { Thread.sleep(2000); j = api("GET", "/jobs/" + job.get("job_id").asText(), null, null); }
while (!Set.of("succeeded", "failed", "cancelled").contains(j.get("status").asText()));
JsonNode reply = M.readTree(j.get("output").get("output").asText());
System.out.println(j.get("charged_credits") + " " + reply.path("task").asText());
if (reply.path("task").asText().equals("slips")) {
for (JsonNode x : reply.path("slips")) System.out.println(x.path("item").asText() + " " + x.path("status").asText());
} else {
System.out.println(reply.path("posture").path("recommended").asText() + " " + reply.path("summary").asText());
}
require "digest"
key = "roadmap-ledger:#{input["task"]}:#{Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]}:a1"
job = api("POST", "/run", input, { "Idempotency-Key" => key })
j = nil
loop do
sleep 2
j = api("GET", "/jobs/#{job["job_id"]}")
break if %w[succeeded failed cancelled].include?(j["status"])
end
reply = JSON.parse(j["output"]["output"])
puts [j["charged_credits"], j["truncated"], reply["task"]].inspect
if reply["task"] == "slips"
reply["slips"].each { |x| puts "#{x["item"]} #{x["status"]}" }
else
puts "#{reply["posture"]["recommended"]} #{reply["summary"]}"
end
$key = "roadmap-ledger:" . $input["task"] . ":" . substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
$job = api("POST", "/run", $input, ["Idempotency-Key" => $key]);
do { sleep(2); $j = api("GET", "/jobs/" . $job["job_id"]); }
while (!in_array($j["status"], ["succeeded", "failed", "cancelled"]));
$reply = json_decode($j["output"]["output"], true);
echo $j["charged_credits"], " ", $reply["task"], "\n";
if ($reply["task"] === "slips") { foreach ($reply["slips"] as $x) echo $x["item"], " ", $x["status"], "\n"; }
else { echo $reply["posture"]["recommended"], " ", $reply["summary"], "\n"; }
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"roadmap-ledger:{input["task"]}:{Convert.ToHexString(digest, 0, 8).ToLowerInvariant()}:a1";
var job = await Api.Call("POST", "/run", input, new() { ["Idempotency-Key"] = key });
JsonNode j;
do { await Task.Delay(2000); j = (await Api.Call("GET", "/jobs/" + job!["job_id"]))!; }
while (!new[] { "succeeded", "failed", "cancelled" }.Contains(j["status"]!.GetValue<string>()));
var reply = JsonNode.Parse(j["output"]!["output"]!.GetValue<string>())!;
var task = (string?)reply["task"];
Console.WriteLine($"{j["charged_credits"]} {task}");
if (task == "slips") { foreach (var x in reply["slips"]!.AsArray()) Console.WriteLine($"{x!["item"]} {x["status"]}"); }
else { Console.WriteLine($"{reply["posture"]!["recommended"]} {reply["summary"]}"); }
Step 6 — POST /run-stream
The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.
# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: roadmap-ledger:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json
rev = json.load(open("input.json")) # the same run input as input.json: {"task": "policy", "company": "Company: Hookwend
The product and why a roadmap now: ...", "items": "Items in the order given (item | phase | date | category | status | changelog | reviews since touched | told):
- Replay failed deliveries from the dashboard | ...", "carry": "", "facts": "The page's checks (...); verdict: fix sales governance before any public page:
- Posture and guardrails | posture | na | ..."}
# - see the input table
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "roadmap-ledger:" + rev["task"] + ":" + hashlib.sha256(json.dumps(rev).encode()).hexdigest()[:16] + ":a1"})
with urllib.request.urlopen(req) as r:
for line in r:
line = line.decode().strip()
if line.startswith("data:"):
print(line[5:].strip()[:200])
const rev = JSON.parse(readFileSync("input.json", "utf8"));
const r = await fetch(BASE + "/run-stream", { method: "POST", body: JSON.stringify(rev), headers: {
"Content-Type": "application/json", Authorization: "Bearer " + TOKEN,
"Idempotency-Key": `roadmap-ledger:${rev.task}:${createHash("sha256").update(JSON.stringify(rev)).digest("hex").slice(0, 16)}:a1` } });
const dec = new TextDecoder();
for await (const chunk of r.body) for (const line of dec.decode(chunk).split("\n"))
if (line.startsWith("data:")) console.log(line.slice(5).trim().slice(0, 200));
rraw, _ := os.ReadFile("input.json") // add "bufio", "strings" to the imports
rs := sha256.Sum256(rraw)
req, _ := http.NewRequest("POST", base+"/run-stream", bytes.NewReader(rraw))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", "roadmap-ledger:"+input["task"].(string)+":"+hex.EncodeToString(rs[:8])+":a1")
res, _ := http.DefaultClient.Do(req)
sc := bufio.NewScanner(res.Body)
for sc.Scan() { if l := sc.Text(); strings.HasPrefix(l, "data:") { fmt.Println(l[5:]) } }
String rev = java.nio.file.Files.readString(java.nio.file.Path.of("input.json"));
var rd = java.security.MessageDigest.getInstance("SHA-256").digest(rev.getBytes());
var sreq = HttpRequest.newBuilder(URI.create(BASE + "/run-stream")).header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token).header("Idempotency-Key", "roadmap-ledger:" + input.get("task") + ":" + HexFormat.of().formatHex(rd, 0, 8) + ":a1")
.POST(HttpRequest.BodyPublishers.ofString(rev)).build();
C.send(sreq, HttpResponse.BodyHandlers.ofLines()).body().filter(l -> l.startsWith("data:")).forEach(System.out::println);
rev = File.read("input.json")
uri = URI(BASE + "/run-stream")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer #{TOKEN}",
"Idempotency-Key" => "roadmap-ledger:#{JSON.parse(rev)["task"]}:#{Digest::SHA256.hexdigest(rev)[0, 16]}:a1")
req.body = rev
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |h|
h.request(req) { |res| res.read_body { |chunk| chunk.each_line { |l| puts l if l.start_with?("data:") } } }
end
$rev = file_get_contents("input.json");
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $rev, CURLOPT_HTTPHEADER => [
"Content-Type: application/json", "Authorization: Bearer $TOKEN",
"Idempotency-Key: roadmap-ledger:" . json_decode($rev, true)["task"] . ":" . substr(hash("sha256", $rev), 0, 16) . ":a1"],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { foreach (explode("\n", $chunk) as $l) if (str_starts_with($l, "data:")) echo $l, "\n"; return strlen($chunk); }]);
curl_exec($ch);
var rev = File.ReadAllText("input.json");
var rd = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(rev));
var sreq = new HttpRequestMessage(HttpMethod.Post, Api.Base + "/run-stream") { Content = new StringContent(rev, System.Text.Encoding.UTF8, "application/json") };
sreq.Headers.Add("Authorization", "Bearer " + Api.Token);
sreq.Headers.Add("Idempotency-Key", $"roadmap-ledger:{input["task"]}:{Convert.ToHexString(rd, 0, 8).ToLowerInvariant()}:a1");
using var http = new HttpClient();
using var res = await http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
using var rdr = new StreamReader(await res.Content.ReadAsStreamAsync());
for (string? l; (l = await rdr.ReadLineAsync()) != null;) if (l.StartsWith("data:")) Console.WriteLine(l);