Seed catalogue · Status
Lamp
A status page and incident communication desk for one business.
- Alternative to
- Statuspage, under MIT
- Domain
- incident on a component
- Version
- 1.1.0
- Contract
- 88 clauses across 12 areas
- Maturity
- ui-port-local-runtime






6 screenshots from the product's own visual and verification artifacts. Its handover records what was exercised.
What you get
Everything below is generated from the seed's own manifest and contract. No rounded-off claims, no numbers we made up.
Operating cost
Runs on your cloud's free tier
The seed declares the free plan as the default deployment. Its own estimate, thresholds and sources are below, unedited.
- Status
- computed from published Cloudflare Workers Free/Paid pricing tiers and the stated usage assumptions, not measured against a running deployment
- Currency
- USD
- Per Month Estimate
- 0
- Basis
- The low-volume default is USD 0/month on Workers Free: 100,000 Worker requests/day, 10,000 Queues operations/day, D1's 5 million reads/day and 100,000 writes/day, the R2 free tier, Cron, and SQLite-backed Durable Objects including alarms. The declared 5,000 public requests/second capacity (or any other Free-plan cap) requires Workers Paid; USD 5/month is the Paid minimum, not a sustained-load estimate. Transactional mail and optional AI remain excluded.
Externals declared
4 externals, each with a reason
Under BASE-DATA-004, user data leaves only through what is declared here.
- mail-sender
- Cloudflare Workers cannot deliver email to arbitrary recipients. Subscriber notification is the product's second surface, and SUB-006 and NOTIFY-008 require authenticated bulk sending with List-Unsubscribe support.
- ai-provider
- DRAFT-001 only. Absent, the composer shows no suggestion control and no request is made (DRAFT-003). Internal notes are never sent (DRAFT-002).
- subscriber-webhooks
- SUB-009. The destinations are chosen by the buyer's subscribers, so they cannot be enumerated as named hosts at publish time; this entry is declared as a class of destinations, not one named service, which satisfies BASE-DATA-004 without a supersession because the payload carries no personal data, every request is signed, and every delivery is in the log.
- run-edit-run-editor
- Optional owner-enabled editor frame; separate Run Edit Run authentication and installation authority are required. Disabled by default.
Extension points
32 declared integration conveniences
These hooks are optional conveniences. Owners may change any source, including core logic, schema and infrastructure; their agents adapt useful upstream fixes and verify the result.
Declared limits
Capacity, stated up front
Straight from seed.json → limits. Where a value is an estimate the seed says so.
- Status
- Mixed evidence: limits marked estimate:true are modeled or locally measured only and still require deployment- or account-specific validation; estimate:false values are enforced configuration limits. Each entry records its evidence.
- Subscribers
- Value 1,000
- Estimate yes
- Why Local D1 engine benchmark: 1,000 active email subscribers were captured into frozen envelopes and dispatched with persisted provider attempts in 224,093ms through a test-only 100ms acknowledgement and Resend's documented default 10 request/second rate model (observed peak 5/s). This is not an account-specific provider acceptance guarantee: configured team quota, external latency, DKIM header coverage, tracking settings, domain authentication, and recipient delivery require deployment verification.
- Components
- Value 200
- Estimate yes
- Component Groups
- Value 25
- Estimate yes
- Open Incidents
- Value 25
- Estimate yes
- Incident Updates Per Incident
- Value 200
- Estimate yes
- Public Page Requests Per Second
- Value 5,000
- Estimate yes
- Why Snapshot served from cache; PAGE-008 requires 200 for all of them.
- Subscribe Requests Per Minute Per Address
- Value 5
- Estimate no
- Why Rate limit on a BASE-ACCESS-003 write path; not a capacity number.
- Intake Requests Per Minute Per Source
- Value 120
- Estimate yes
- Upload Max Bytes
- Value 2,097,152
- Estimate no
- Why Page logo only (BASE-INPUT-003).
- Upload Types
- Value image/png, image/jpeg, image/webp
- Estimate no
- Why No SVG: BASE-INPUT-002 would have to sanitise a scripting format for one logo, on the one surface that must never run anything unexpected.
- Snapshot Max Bytes Gzipped
- Value 51,200
- Estimate no
- Why PAGE-003.
Non-goals, stated proudly
A product that says what it will never do is a product you can trust to stay small, fast and legible. These are refusals, not gaps.
- Uptime monitoring, probing, or synthetic checks: Lamp reports state that operators or accepted alerts produce, and running probes would make it a monitoring product with a second, contradictory source of truth behind the public page.
- On-call scheduling, paging, and escalation: the buyer's existing alerting tool already does this, and it is the system that feeds INTAKE-001.
- More than one status page per deployment: one business gets one public record, so a second page is a second deployment.
- Private or audience-scoped status pages: visitor authentication would be a whole second access surface for a product whose value is being readable by anyone, at any time, with nothing between them and it.
- Roles and permissions: these are one business's equal operators, and UPDATE-007 covers the one gate buyers actually ask for.
- SMS and voice notifications: per-message cost, carrier registration, and per-country consent rules that a self-hosted deployment cannot satisfy generically.
- Multi-language status pages: every incident update would need a translation before publish, which is the opposite of publishing in two minutes.
- A general-purpose operator API authenticated by API tokens: BASE-ACCESS-002 requires a session on every operator endpoint, and machine writes have one narrow door, INTAKE-001, with its own secret.
- SSO and SCIM: magic-link sign-in per BASE-ACCESS-001 is enough for a handful of operators, and an identity provider is an adapter, not a feature.
- Visitor analytics and email open tracking: BASE-PUBLIC-001 keeps the page free of cookies and visitor data, and NOTIFY-004 keeps tracking pixels out of notifications, because knowing who read an outage notice is worth less than being the one page a customer can open without being counted.
- Native mobile apps: the public page is the mobile experience, and it has to work with script disabled.
The contract
All 88 clauses of Lamp, from its own CONTRACT.md. Stable IDs identify requirements; the source handover and verification evidence distinguish implemented, tested and uncovered behavior.
PAGE14 clauses
GET / on the status page host returns, in one response: the overall status; every visible component with its current state and note; every unresolved incident with its 5 most recent published updates, newest first, and a link to that incident's page carrying all of them; every maintenance that is in progress and every scheduled maintenance starting within 14 days; and each visible component's uptime figure and daily record for the reporting window.
The complete content of PAGE-001, and the subscribe form of SUB-001, are present and usable in the initial HTML response. With JavaScript disabled, every status, update, and timestamp is rendered and readable and the subscribe form submits. Script only replaces timestamps with the visitor's local-timezone equivalent.
At the component limit in seed.json → limits, the public page's initial HTML response is at most 50KB gzipped, and the page issues at most 4 further requests — stylesheet, font, logo, script — all to its own origin.
Policy: page.status-rollup.v1; default: the overall status is the most severe current state among visible components, under the order in COMP-001. It is shown as one labelled state at the top of the page. With every visible component Operational and no unresolved incident, that label reads as operational.
Every change to the content in PAGE-001 writes a complete rendered snapshot of the public page, the Atom feed, and the widget payload to object storage. Serving those three surfaces reads the snapshot and issues no database query. A snapshot write that fails is retried until it succeeds and is reported as a failed job under BASE-OPS-004; the previous snapshot keeps serving until it does.
When the database, the queue, or object storage is unreachable, the three surfaces in PAGE-005 return 200 — from the current snapshot, or from the cached response under the stale-if-error directive of PAGE-007 when the origin itself cannot answer. They return 503 only when no snapshot has ever been written. A response that could not be built from current data states the instant its content was generated and is labelled as the last known state; no failed read ever produces the all-clear of PAGE-004. /health still reports 503 per BASE-OPS-001; the public page's availability does not depend on it.
The three surfaces in PAGE-005 are served with Cache-Control: public, max-age=10, s-maxage=10, stale-while-revalidate=30, stale-if-error=3600. An update, component state change, or maintenance transition purges that cache and is readable on all three surfaces within 10 seconds of the request that caused it returning 200.
The public read paths marked read in seed.json → deploy.publicPaths never return 429. Requests from one address up to the publicPageRequestsPerSecond limit in seed.json → limits all return 200. Supersedes BASE-ACCESS-003 for those paths only. A status page is read hardest at the moment it matters most, and a per-address limit would reject real readers behind one egress address during an incident — but the decisive reason is mechanical: RFC 6585 requires that a 429 is never stored by a cache, so rate limiting the read path converts a cacheable traffic spike into an uncacheable one and sends it all to the origin. What replaces the limit is the cache itself: those paths return identical bytes to every visitor between snapshots, so origin load is a function of the PAGE-007 window and not of how many people are reading. The public write paths (/subscribe, /unsubscribe, /manage, /api/intake/*) stay rate limited under BASE-ACCESS-003.
Every timestamp on a public surface or in a notification is rendered in the page's configured timezone with its UTC offset, and every timestamp on the page carries the same instant as an ISO 8601 value in a machine-readable attribute.
/feed.atom validates as Atom 1.0 and contains one entry for every published incident update and for every maintenance notice — scheduled, rescheduled, started, completed, cancelled — from the last 12 months, ordered newest first. An entry's identifier is stable from first publication and is never reused.
One script tag renders a banner showing the overall status and any unresolved incident, read from the PAGE-005 snapshot. The script is at most 10KB gzipped, loads only from the deployment's origin, and renders only on the host origins listed in settings per BASE-PUBLIC-002.
Page name, logo, accent colour, timezone, hostname, and a footer-credit toggle are set in settings without a code change. The footer credit is the plain static text Powered by Lamp · runeditrun.com, defaults on, and disabling it makes no network action. The logo is served from the deployment's own origin. The favicon composites a mark for the current overall status over the configured logo rather than replacing it, and the page carries Open Graph metadata whose title and image state the current overall status.
Every public page response carries a Content-Security-Policy whose default-src, script-src, style-src, img-src, connect-src, form-action, and base-uri name only the deployment's own origin, and frame-ancestors 'none', together with X-Content-Type-Options: nosniff and Referrer-Policy: strict-origin-when-cross-origin. The page contains no CAPTCHA and no consent banner, because it stores nothing on the visitor's device and loads nothing that would need either.
When script is available the page re-reads /api/public/status at most every 30 seconds and updates the overall status and the newest update in place. That response is a fixed set of fields whose size does not grow with the number of components. The region it updates is an ARIA live region, so a change is announced without a reload. With script disabled the page does not refresh itself and states the instant its content was generated.
COMP7 clauses
A component's public state is exactly one of Operational, Degraded performance, Partially unavailable, Major outage, or Under maintenance. Those states are ordered by severity, most severe first: Major outage, Partially unavailable, Degraded performance, Under maintenance, Operational. Every clause that says "most severe" means this order.
A component belongs to at most one group. Operators set the order of groups and of components within a group, and the public page renders them in that order.
Every change to a component's public state appends an entry recording the previous state, the new state, the instant of the change, and the operator or incident that caused it. An entry is never modified or removed; a correction under UPTIME-004 appends a superseding entry rather than altering the one it corrects.
A component has a note of at most 250 characters, empty by default. When it is not empty it is shown beside that component's state on the public page exactly as entered.
A component marked internal is absent from the public page, the Atom feed, the widget, and every uptime figure, and never contributes to the overall status.
Three things claim a state for a component: an unresolved incident, an in-progress maintenance, and an operator's standing claim set outside either. An incident or maintenance claims a state per attached component, chosen when the component is attached. A component's public state is the most severe of the states claimed for it, and Operational when nothing claims it. Resolving an incident, completing a maintenance, attaching or detaching a component, and setting or clearing an operator claim each recompute that component's state by this rule within 10 seconds.
Deleting a component removes it and its state history from the public page and from every uptime figure within 10 seconds. Incidents and maintenance that referenced it keep the component's name in their published updates.
UPTIME6 clauses
For each visible component the page shows the percentage of the reporting window during which the component was not in a downtime state, computed from COMP-003 state history at one-minute resolution and rounded down to two decimal places, so a window containing any downtime never shows 100.00%. The reporting window is 90 days.
Policy: uptime.downtime-states.v1; default: a minute in Major outage or Partially unavailable counts as one downtime minute, a minute in Degraded performance counts as half a downtime minute, and a minute in Under maintenance counts as none. Degraded performance is not weighted at zero: most published incidents never get worse than it, and discarding them is what makes an uptime figure disagree with the incident history printed beneath it.
A component whose earliest state history entry is later than the start of the reporting window shows uptime computed from that entry, and the page labels the figure with the number of whole days it covers.
An operator can correct a closed interval of a component's state history, stating the correct state for that interval and a reason. The correction appends to the history per COMP-003, recomputes every uptime figure it affects within 60 seconds, and records the operator, the interval, and the reason in the audit log. A request without a reason is rejected with 400.
Beside the uptime figures the page names each state's weight under UPTIME-002 and the length of the window under UPTIME-001, in text present in the initial HTML. Because UPDATE-001 refuses to publish an update that claims no component, every published incident moves these figures; none of the incident history printed on the same page is invisible to them.
The page shows one mark per day of the reporting window for each visible component. A day containing any downtime minute is labelled with the number of downtime minutes in that day. A day before the component's earliest state history entry is rendered as having no data and is visually distinct from a day with no downtime.
INCIDENT9 clauses
An incident is in exactly one of Investigating, Identified, Monitoring, or Resolved. It is created in Investigating, and its state changes only through a published update that sets it.
An incident's impact is the most severe state it has claimed on any attached component at any time since it was opened, so a resolved incident keeps the impact it reached. An incident with no attached components has no impact and cannot be published; UPDATE-001 requires at least one.
Every incident has a reference of the form INC- followed by digits and a permanent public URL containing it. A reference is never reused, including after INCIDENT-007.
A published update is never edited or deleted. A correction is a new published update that names the update it corrects, and both stay on the public timeline in the order they were published.
An incident resolved in error is reopened by publishing an update that sets it to Investigating. The resolution update and the reopening update both stay on the public timeline.
When the next-update time committed by an incident's latest published update passes with no newer update published, the incident is marked overdue in the operator app. The public page shows the committed time and never labels the incident overdue.
Deleting an incident requires a reason and removes it from the public page, the history page, and the Atom feed within 10 seconds. A request without a reason is rejected with 400. The operator, the reason, and the time are recorded in the audit log.
An operator can open an incident whose updates carry publication times in the past. Those times are what the public timeline and the history page show, the component states it claims are written into the state history at those times so UPTIME-001 counts them, and no subscriber is notified for any of its updates.
An incident carries internal notes visible only to a signed-in operator. A note never appears on the public page, in the Atom feed, in the widget, in any notification, or in any AI request.
UPDATE9 clauses
Publishing an update requires at least one attached component, an affected-audience statement of at most 250 characters, a customer-impact statement of at most 250 characters, and a next-update time. A what-still-works statement of at most 250 characters and a details statement of at most 500 characters are optional. Every field is plain text; no HTML or markdown is interpreted. A publish request that omits a required field, exceeds a length, or attaches no component returns 400 and changes nothing. The next-update time is a commitment to publish again by then; it is never presented as an estimate of when the incident will be resolved.
The public page, the Atom feed, the widget payload, and the notification email render an update from the same renderer. No surface omits a populated field of the update or shows a field the update does not carry.
Every published update sets the incident's lifecycle state. An update that sets Resolved records the incident's end time and recomputes every attached component under COMP-006.
Before publishing, the composer shows the update as the public page and as the notification email will render it, produced by the renderer named in UPDATE-002.
An unpublished update appears on no public surface. A draft is saved within 2 seconds of the last edit and is restored unchanged after a browser reload.
An update given a future publish time publishes within 60 seconds of that time and appears on no public surface and in no notification before it. If UPDATE-007 review is in force and the update has not been reviewed when that time arrives, it is not published, stays a draft, and is reported under NOTIFY-006.
Policy: update.review.v1; default: no review is required. When review is required, a publish request for an update its author wrote is rejected with 409 until an operator other than the author marks it reviewed; the reviewer and the time are recorded on the update.
One transaction commits the update together with the recipient set it will notify on each channel selected under NOTIFY-007. That set is fixed at publish time: a subscriber who narrows their selection afterwards still receives this update, and one who unsubscribes afterwards does not, per SUB-008. When the publish request returns 200 that transaction is committed, and PAGE-007 and NOTIFY-002 follow from it. When it returns an error the update is on no surface and no recipient is recorded for it. Dispatch of the committed set is retried until it succeeds and never blocks the publish.
An operator can save a template holding the update fields of UPDATE-001, the components it attaches with the state each claims, and the lifecycle state it sets. Applying a template fills the composer and attaches those components with those states, and publishes nothing.
MAINT6 clauses
A scheduled maintenance has a start time, an end time, and attached components. It has a reference of the form SCH- followed by digits, and is listed separately from incidents wherever both appear.
A scheduled maintenance moves from Scheduled to In progress within 60 seconds of its start time, and from In progress to Completed within 60 seconds of its end time, with no operator action.
A maintenance claims Under maintenance for every attached component while it is In progress, and claims nothing before it starts or after it completes. A component's public state follows from COMP-006 throughout, so a state an operator or an incident claimed before the window is still claimed after it.
A maintenance still In progress 15 minutes after its end time is shown on the public page as running longer than planned, beside its original end time, until an operator completes it or extends the window.
Policy: maintenance.reminders.v1; default: subscribers are notified when the maintenance is scheduled, 24 hours before the start time, when it moves to In progress, and when it moves to Completed.
Changing the window of a scheduled maintenance notifies every subscriber already notified about it. Cancelling it publishes a cancellation notice to those same subscribers, removes it from the upcoming list within 10 seconds, and keeps it in the maintenance history marked cancelled.
SUB12 clauses
Submitting an email address on the public page creates a pending subscription and sends one confirmation email to that address within 60 seconds. That address receives at most one confirmation email in any 24 hours however many times it is submitted, which is what bounds a subscription-bombing attack. A pending subscription receives no other notification. The form is a standard POST that works with script disabled, is rejected with 403 when the request carries Sec-Fetch-Site: cross-site, and carries no CSRF token, because with no cookie and no session there is no ambient authority to forge.
Policy: subscriber.confirmation.v1; default: a subscription becomes active only when the link in the confirmation email is followed.
A pending subscription not confirmed within 72 hours is deleted, including the address it holds. Supersedes BASE-DATA-002 for pending subscriptions only: the address was supplied by an unverified visitor who may not own it, so it is held only as long as confirmation is possible.
A subscriber selects which components they want updates about, and is notified about an incident or maintenance only when it claims a state for at least one component in that selection. A subscription whose selection has never been narrowed follows every visible component, including components added after it was created. A subscription with a narrowed selection is unchanged when a component is added. When a component is attached to an unresolved incident, every subscriber who follows that component and has received no notification for that incident receives exactly one notification carrying its most recent published update, and then receives subsequent updates normally.
A subscriber is identified on public surfaces only by an opaque token of at least 128 bits of entropy that addresses one subscription, is carried in the URL of a link the deployment emailed to that subscriber, grants no other capability, and is revoked when the subscription is deactivated or deleted. No cookie is set on any public surface. Responses on token-addressed paths carry Referrer-Policy: no-referrer and X-Robots-Tag: noindex. Supersedes BASE-PUBLIC-001 for token-addressed paths only, which transmit the token on load rather than after a first interaction, because SUB-006 requires an unsubscribe that completes without any interaction on our page.
Every notification email carries a List-Unsubscribe header with a mailto: and an https: value, a List-Unsubscribe-Post: List-Unsubscribe=One-Click header, and a visible unsubscribe link in the body. A POST to the https: value, with a body of List-Unsubscribe=One-Click in either multipart/form-data or application/x-www-form-urlencoded, deactivates that subscription and returns 200 with no redirect, no cookie, and no further interaction. Both headers are named in the message's DKIM h= tag, without which receivers decline to offer one-click at all. That endpoint's BASE-ACCESS-003 rate limit is counted per subscription token, never per source address, so one mailbox provider's shared egress cannot exhaust the limit for other subscribers.
The token URL in every notification opens a page that shows the subscription's channel and component selection and lets the subscriber change the selection or unsubscribe, without a session.
After a subscription is deactivated, no notification is delivered to it. A notification committed before the deactivation and dispatched after it sends nothing and records the outcome as suppressed.
An endpoint URL and a contact email address together subscribe as a webhook subscriber; the address exists so SUB-010 can say why the endpoint was deactivated. Each notification is a POST carrying webhook-id, webhook-timestamp, and webhook-signature headers, where the signature is a space-separated list of versioned HMAC-SHA256 values over {webhook-id}.{webhook-timestamp}.{raw body}, so a secret can be rotated without a gap. The body carries the incident, every update it has published, and each attached component's previous and new state, so a receiver that missed a delivery needs no prior state. It carries no subscriber address. A delivery succeeds only on a 2xx within 30 seconds; a redirect is a failure. A Slack incoming-webhook URL receives the same events with a Slack-formatted body.
An address that hard-bounces once, or whose provider reports a spam complaint, is deactivated with that reason recorded. An address that soft-bounces on 5 consecutive notifications, and a webhook endpoint whose last 10 consecutive deliveries all failed under SUB-009, are deactivated with that reason recorded. Deactivating a webhook endpoint emails its contact address. A deactivated destination receives no further notification.
Operators see every subscriber with channel, state of pending, active, or deactivated, component selection, and the reason and time of any deactivation. Deleting a subscriber deletes the address or endpoint it holds.
Submitting an address that already has a subscription returns the same status and body as submitting a new address and creates no second subscription. That address receives one email saying it is already subscribed, carrying its management link and nothing else.
NOTIFY10 clauses
Policy: notification.events.v1; default: an incident notifies on its first published update, on every subsequent published update, and on the update that resolves it; a scheduled maintenance notifies per MAINT-005; a component state change made outside an incident or maintenance notifies no one.
At the subscriber limit in seed.json → limits, every eligible email notification for a published update is accepted by the mail provider within 5 minutes of the publish request returning 200.
A notification that fails with a retryable error is retried 5 times with exponential backoff over 1 hour. After the last attempt it is recorded as failed against that subscriber and that update.
Every notification records the subscriber, channel, update, queue time, outcome, and outcome time. An outcome is one of queued, delivered, bounced, complained, suppressed, or failed. No notification carries a tracking pixel or a link that records who followed it, so no outcome describes reading. The log is filterable by channel, outcome, and update, and exports as CSV.
A subscriber receives at most one notification per published update per channel, whatever the number of times the publish is retried or the queue delivers the job.
An operator email is sent, at most once per event, when an incident becomes overdue under INCIDENT-006, when a maintenance overruns under MAINT-004, when a scheduled update is held back for review under UPDATE-006, when an alert creates a draft incident under INTAKE-002, and when a notification is recorded as failed under NOTIFY-003.
The operator selects, for each update, which subscriber channels receive it, and selecting none is allowed. The public page, the Atom feed, and the widget always receive the update and cannot be deselected. The delivery log records the selection.
The deployment sends email only to an operator, to an address with a pending subscription for its single confirmation email, and to an address with an active subscription. Every notification is sent through mail.sender.v1 from the address configured in settings.
Every notification email carries List-Id, List-Archive, and List-Help identifying the page, Auto-Submitted: auto-generated so it triggers no vacation reply, and, for every update after an incident's first, In-Reply-To and References naming the message that carried the first, so a mail client groups an incident's updates into one thread. pnpm setup fails naming the record when the From address's domain publishes no SPF, no DKIM key for the configured selector, or no DMARC policy.
A publish under UPDATE-008 returns 200 while the mail provider, the queue, or a subscriber endpoint is failing. The public page, the feed, and the widget update under PAGE-007 regardless, and the committed notification set is delivered under NOTIFY-003 when the channel recovers.
INTAKE5 clauses
Each source is configured with one of two credentials. With a signing secret, a request is accepted only when the HMAC-SHA256 over its timestamp header and raw body matches and the timestamp is within 5 minutes of the deployment's clock; the signature is verified over the received bytes before they are parsed. With a bearer token, a request is accepted only when it presents that token, which exists because most monitoring tools — among them Datadog, Pingdom, and Uptime Kuma — cannot sign a webhook at all. A request with an absent, malformed, stale, or non-matching credential returns 401 and creates nothing.
Policy: alert.intake.v1; default: an accepted alert creates or updates a draft incident within 30 seconds and changes no public surface. An operator publishes it under UPDATE-008. A buyer who wants a monitored component to change state without waiting for a person replaces this policy; the narrative still waits for an operator, because no alert payload contains the UPDATE-001 fields.
Alerts carrying the same deduplication key produce one incident for as long as that incident is unresolved: an alert whose key matches an unresolved incident is recorded against that incident and creates no second one. A redelivery of a request already accepted creates no additional incident, draft, or alert record and returns the same status as the first delivery.
An alert marked as recovered for a deduplication key records the recovery time on the matching alert record and changes no public surface and no incident lifecycle state.
Each source configures, as JSON paths into its own payload, which field supplies the deduplication key, which supplies the title, and which distinguishes a firing alert from a recovered one. A request whose payload lacks the configured deduplication-key path returns 400 and is recorded against that source where an operator can see it.
DRAFT3 clauses
When an AI provider is configured, an operator can request a suggested update for an incident. The suggestion fills the composer's fields, is editable in every field, and is not published.
A suggestion is built from the incident's accepted alerts, its published updates, its attached components and their claimed states, and the tone instructions configured in settings. Internal notes under INCIDENT-008 are not sent.
AI calls go through ai.provider.v1. With no provider configured the composer shows no suggestion control and the deployment makes no AI request. When a configured provider fails or does not answer within 20 seconds, the composer shows the error and every other publish path behaves unchanged.
TEAM3 clauses
Every operator can create, edit, publish, and resolve every incident and maintenance, change every component, and manage subscribers. There are no roles. The only gate on publishing is UPDATE-007.
Operators are invited and removed in settings. The operator configured at setup can be removed only by themselves.
Every published update, component state change, maintenance change, history correction, and subscriber deletion records the operator who made it. That operator's name is shown in the operator app and on no public surface.
HIST4 clauses
The history page lists resolved incidents and completed maintenance grouped by month for the last 12 months, each with start time, end time, duration, the components it affected, and every update it published.
An operator can attach one post-incident report to a resolved incident, holding a summary, a timeline, a cause, and remediation. It appears on that incident's public page within 10 seconds of publishing, and notifies subscribers on the channels the operator selects for it in the same way NOTIFY-007 selects them for an update.
Policy: postmortem.due.v1; default: an incident that claimed Partially unavailable or Major outage on any component and has no post-incident report 7 days after resolution is marked as owing one in the operator app.
The export contains every incident, update, component with its full state history, maintenance, subscriber with channel and state, notification delivery record, post-incident report, setting, and audit entry as JSON, and every uploaded image as its original file.
Baseline guarantees
22 declared clauses from this product's BASELINE.md. Selected ownership guarantees are shown below; execution evidence is separate.
The deployment makes no request to the marketplace or the seed author. There is no telemetry, phone-home, licence check, or kill switch.
pnpm export produces every record and attachment; pnpm import restores it into an empty deployment; export, import, export yields an archive with equivalent contents.
User data leaves the deployment only through externals declared in seed.json → externals. No other outbound request carries user data.
Public surfaces (widgets, booking pages, forms, status pages) set no cookies, load nothing from third-party domains, and send no visitor data before the visitor's first interaction.
No secret appears in the client bundle, in logs, or in an error response.
Release history
Honest version: there is one release line so far, and we will not invent the rest.
- v1.1.0
- Current — ui-port-local-runtime
The manifest states this version and maturity. Consult the source verification evidence before deployment.
Setup status and DIY
Lamp guided setup has been demonstrated in a controlled live pilot; public self-service qualification is pending. Use the source instructions below for current installation commands and requirements.
Use the edition's source README for its available installation commands and required configuration.
View as agent
Everything on this page exists as machine-readable files. If you are an agent evaluating Lamp for someone, read these instead of the prose.
Installation instructions are in the source README; this entry does not declare a catalogue install command.
- README.md
- Source setup, available commands and implementation status.
- seed.json
- The manifest: capabilities mapped to clauses, externals, extension points, limits, operating cost, deploy requirements.
- CONTRACT.md
- The 88 clauses above, in source form.
- BASELINE.md
- The product's declared baseline requirements.
- AGENTS.md
- Instructions written for the coding agent that will maintain this deployment.
- /llms.txt
- The site, for machines.
Catalogue endpoint
/agents/catalogue.json
Every seed with its id, category, version, clause count, install command and links to the files on the left. Generated from the seeds' own artifacts on every build, so it cannot drift from what ships.

Run Lamp on your own cloud.
MIT code, your data, your infrastructure. Leave whenever you like and it keeps running.