Seed catalogue · Records
Cradle
A self-hosted launch-portfolio operating system for one business, focused on campaign readiness, risks, milestones, decisions, saved lenses, and visible automation.
- Alternative to
- Airtable, under MIT
- Domain
- table with typed fields and views
- Version
- 0.0.0-bootstrap
- Contract
- 97 clauses across 16 areas
- Maturity
- ui-ported-thin-data-spine






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
- estimated on 2026-08-31 from published Cloudflare pricing, not measured against a running deployment
- Currency
- USD
- Estimate
- $0 per month on Workers Free for the default deployment, excluding usage charged by the selected mail and AI providers
- Assumptions
- Static assets, up to 100,000 Worker requests/day, 5 million D1 rows read/day, 100,000 D1 rows written/day, 5 GB total D1 storage, 10,000 Queue operations/day, five Cron Triggers, SQLite-backed Durable Objects (including alarms), and the R2 Standard free tier are available without a Workers Paid subscription. The declared record-count limits are capacity targets and do not by themselves require Workers Paid. Upgrade before production traffic or processing crosses a Workers Free hard cap—most likely 100,000 Worker requests/day, 5 million D1 rows read/day, 100,000 D1 rows written/day, 5 GB D1 storage, or 10,000 Queue operations/day. R2 usage beyond 10 GB-month, 1 million Class A operations, or 10 million Class B operations/month is billed separately. Adapter usage varies by provider.
- Sources
- developers.cloudflare.com/workers/platform/pricing/
developers.cloudflare.com/workers/platform/limits/
developers.cloudflare.com/d1/platform/pricing/
developers.cloudflare.com/r2/pricing/
developers.cloudflare.com/queues/platform/pricing/
developers.cloudflare.com/durable-objects/platform/pricing/
Externals declared
2 externals, each with a reason
Under BASE-DATA-004, user data leaves only through what is declared here.
- transactional-mail
- Magic-link sign-in and the operator notifications in NOTIFY-002 must reach email addresses outside the deployment.
- ai-provider
- The campaign briefs, risk readouts, and lens proposals in AI-001 require a model; with AI off under SET-002 the rest of the product is unchanged.
Extension points
48 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
- ui-ported-estimates-not-load-tested
- Operators
- 25
- Campaigns
- 50,000
- Campaign Custom Fields
- 50
- Deliverables
- 500,000
- Milestones
- 250,000
- Risks
- 250,000
- Decisions
- 250,000
- Saved Lenses
- 200
- Lens Conditions
- 20
- Automations
- 100
- Bulk Selection Records
- 500
- Csv Import Bytes
- 10,485,760
- Csv Import Rows
- 10,000
- Csv Import Mime Types
- text/csv, application/vnd.ms-excel, text/plain
- Request Body Bytes
- 1,048,576
- Public Requests Per Ip Per Minute
- / 120
- /health 120
- /signin 120
- /auth/request 20
- /auth/callback 20
- /assets/ 600
- /favicon.ico 120
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.
- multiple workspaces or tenants
- roles or granular permissions
- arbitrary tables, schema design, or user-defined formula and rollup fields
- general app building, scripting, or generic webhooks
- public or embeddable views
- bidirectional Airtable, spreadsheet, or SaaS sync
- automatic dependency rescheduling or critical-path optimisation
- resource or capacity planning
- general task management
- autonomous AI mutations or outbound communication
- notification channels other than in-app and email
- file attachments on records
- editorial content calendars and customer-feedback tracking
- native mobile applications
The contract
All 97 clauses of Cradle, from its own CONTRACT.md. Stable IDs identify requirements; the source handover and verification evidence distinguish implemented, tested and uncovered behavior.
HOME1 clause
At /, this internal tool presents a public identity plate using values in src/ext/config.ts and the configured theme tokens: the owner's configured name, one configurable description of their launch workspace, and a link to /signin. Its optional footer credit is visible by default and config can turn it off; when visible it names the configured product and links statically to https://runeditrun.com. The credit adds no telemetry or phone-home. The landing contains no record content, public views, pricing, feature tour, testimonials, or metrics.
OPS6 clauses
Every operator can read every record in the workspace and perform every mutation in this contract. The only restrictable action is inviting and removing operators, under operator.management.v1. There are no roles.
Operators are invited and removed in settings. The operator configured at setup can be removed only by themselves, so every operator record stays removable and erasable by an operator. Policy: operator.management.v1; default: any operator may invite or remove.
Setup requires a valid IANA workspace timezone and rejects any other value with a validation error. Changing it re-renders every displayed time and moves the next fire time of every schedule in this contract; it leaves stored UTC timestamps byte-identical and moves no run already queued and no email already sent.
Removing an operator ends their sessions within 1 minute, drops them from digest recipients, and cancels their outstanding readiness requests. Removal is rejected, naming each blocking record, while that operator owns an active campaign or is the assignee of an unresolved decision. Risks, deliverables, milestones, comments, activity entries, and AI analyses keep their attribution to the removed operator, and a lens condition naming them returns zero results rather than an error.
A removed operator can be erased under BASE-DATA-003. Erasure replaces their name and email with a stable anonymous label wherever they appear, deletes their notifications and delivery records, keeps the comments, activity entries, and decision outcomes they authored under that label, completes within 5 minutes, is confirmed when complete, and cannot be undone.
Setup requires an ISO 4217 workspace currency. Budget and spend are stored as minor-unit integers in that currency and are displayed, summed, and exported in it. Changing the currency relabels stored amounts and never converts them.
VIEW7 clauses
The operator app exposes Workspace Home, Portfolio Overview, Campaign Grid, Risks Lens, Timeline Lens, Campaign Detail, Lens Builder, Decision Queue, and Automation Monitor, each at its own route and each reachable from one navigation shell present on all of them.
Selecting a campaign, risk, milestone, or decision shows that record's own fields and the records linked to it. No field and no identifier of a previously selected record stays visible.
A read issued after a mutation's success response returns that mutation's committed state and the counts derived from it on Workspace Home, Portfolio Overview, Campaign Grid, Campaign Detail, every lens, Decision Queue, and Automation Monitor.
Every collection and detail surface renders exactly one of loading, empty, not found, signed out, or failed. The failed state names the operation that failed and offers a retry; it renders no rows, no sample data, and no result of an earlier successful read. An action a policy forbids is refused with a stated reason and changes nothing.
Global search matches a case-insensitive token prefix of a campaign name, code, or summary, or of a deliverable, risk, milestone, or decision title, returns at most 20 results per record type each linking to that record's detail, and responds within 500ms. Comment text is not searched. Records on archived campaigns are returned only when the search explicitly includes archived campaigns.
Every mutation of a campaign, deliverable, milestone, risk, decision, comment, lens, custom-field definition, automation, or workspace setting supplies that record's current version. A mutation carrying an older version returns 409, names the current version, and changes nothing.
A collection response returns at most 200 records with a cursor, applies the requested sort with the record ID as its final tiebreak so paging repeats and skips nothing, and reports an exact total up to 10,000 matches and 10,000+ beyond it. A result larger than one page states how many records are shown out of that total.
CAMP14 clauses
An operator can create, edit, archive, and delete a campaign. It requires a code, a name, an owner, a start date, and a launch date, and it also carries an objective, a status summary of at most 280 characters, a phase, a status, health, budget, spend, tags, and custom fields.
A campaign is active or archived, and any operator can archive or restore one. Archiving deletes nothing. Policy: campaign.lifecycle.v1; default phases: Planning, Build, Production, Review, Launch prep; default statuses: On track, Watch, At risk, Blocked; default launch-preparation phase: Launch prep.
A campaign's launch date must not fall before its start date, its health must be a whole number from 0 to 100, and its budget and spend must be zero or greater. Campaign codes are trimmed and unique case-insensitively. A mutation breaking any of these, or clearing the owner, start date, or launch date of an active campaign, is rejected with a validation error naming the field and changes nothing.
Campaign detail shows the campaign's fields, its complete and total deliverable counts, its milestones, its open risks, its unresolved decisions, its comments, and its activity. Those counts are derived: they change only through a mutation to a linked record.
Health is shown with the operator who last set it and when. A campaign whose health has not been set in the last 14 days is marked stale. Policy: campaign.health.v1; default: health and status change only through an explicit operator edit or an import row that maps the field. A replacement may derive health from linked records; no replacement may set it from AI output, which AI-003 forbids.
Portfolio Overview shows Live campaigns as the count of non-archived campaigns, Needs attention as the count of non-archived campaigns whose status is At risk or Blocked, Open decisions as the count of unresolved decisions on non-archived campaigns, and Portfolio health as the arithmetic mean of non-archived campaign health rounded half up to a whole number, or No data when there are none. It states how many campaigns in that mean are stale under CAMP-005.
Any operator can filter, group, sort, and choose visible fields in the campaign grid. Three built-in views ship: All active, every non-archived campaign; Launch prep, every non-archived campaign in the lifecycle policy's launch-preparation phase; and My campaigns, every non-archived campaign owned by the signed-in operator. Each re-queries on open and on refresh, and opening, filtering, grouping, or sorting a view changes no record.
An operator can define, edit, and delete campaign custom fields of the types in seed.json → customFields, and those fields are available in grid display, filtering, lens conditions, CSV import, and CSV export. Deleting a definition deletes its stored values and is confirmed with the number of campaigns holding one.
Archiving a campaign is allowed while it has open deliverables, milestones, risks, and decisions, and withdraws its work in one commit: its unresolved decisions leave Decision Queue, its risks and milestones leave every lens and the digest, its incomplete deliverables and milestones stop counting as overdue, it leaves the counts in CAMP-006 and every search that does not ask for archived campaigns, its outstanding readiness requests and every queued automation run whose subject is that campaign or one of its records are cancelled without executing an action, and email held under NOTIFY-002 for that campaign is dropped. Archiving completes and resolves nothing.
Restoring returns the campaign and every record archived with it to the state each had at archiving, and creates no notification, readiness request, or automation run for the archived period. CAMP-003 is applied at restore: a campaign whose owner was removed while it was archived must be given a current owner before it becomes active.
Deleting a campaign removes its deliverables, its milestones and their dependencies, its risks, decisions, comments, campaign activity, AI analyses, and import references under BASE-DATA-003. Deletion requires typing the campaign code to confirm and cannot be undone. Audit records survive with the campaign reference anonymised.
A campaign's risk rating is derived as the highest severity among its open risks, or None when it has none, and is shown wherever the campaign is listed.
In the campaign grid and in any Grid or Cards lens an operator can select up to 500 records, including every record matching the current conditions, and apply one change to all of them: owner, phase, status, a tag added or removed, a custom-field value, archive, or restore. A bulk change commits for every selected record or none, is rejected naming the first record that fails validation, writes one activity entry on each changed record and one bulk entry naming the operator and the count, and can be undone once within 1 hour, restoring the prior value of every record it changed.
Duplicating a campaign copies its fields, tags, custom-field values, deliverables, and milestones under a new unique code, and copies no risks, decisions, comments, activity, notifications, or AI analyses. When the operator supplies a new launch date, every copied date shifts by the difference between it and the original launch date; otherwise dates are copied unchanged. The copy triggers no automation.
DELIV3 clauses
An operator can create, edit, and delete a deliverable. It belongs to exactly one campaign, requires a title and an owner, has an optional due date, and has a state of Open or Complete that starts Open.
Completing or reopening a deliverable records the operator and the time, adds a campaign activity entry, and changes the campaign's derived completion count in the same commit.
An Open deliverable with a due date before the current date in the workspace timezone is marked overdue on campaign detail, in every lens and search result that contains it, and in CSV export. A deliverable due today is not overdue.
MILE6 clauses
An operator can create, edit, and delete a milestone. It belongs to exactly one campaign, requires a title and an owner, has a type of Launch, Review, Approval, Decision, Event, or Publish, has a status of Not started, On track, Watch, At risk, Blocked, or Complete that starts Not started, and has either a single date or a start and end date whose end does not fall before its start.
As shipped, Timeline Lens contains every milestone of every non-archived campaign, ordered by date ascending using the start date for a range and then by campaign code. A range is rendered as a range with both dates and a point as a point with one date. Month scale renders one column per calendar month and Quarter scale one per quarter in the workspace timezone; switching scale changes only the columns, never the milestone set. Every item links to its campaign detail and to its milestone detail.
An operator can name existing milestones in any campaign as predecessors of a milestone. A predecessor set that would create a cycle is rejected with a validation error and changes nothing. When a milestone starts before a predecessor ends, both carry a dependency-conflict marker on milestone detail and in Timeline Lens, and setting the later milestone's status to On track while that marker stands requires a reason of at least one non-whitespace character, recorded on the milestone and in campaign activity. Changing one milestone's date never changes another's.
A readiness request is an in-app notification addressed to a milestone's owner, Outstanding until that owner acknowledges it. Within 15 minutes of 08:00 each day in the workspace timezone the shipped readiness automation creates exactly one Outstanding request for every milestone of type Launch that is not Complete, whose campaign is non-archived, and whose date falls within the next 5 days, and that has no Outstanding request. A repeated evaluation creates no duplicate.
Moving a Launch milestone's date out of the five-day window cancels its Outstanding request, and moving it inside the window creates one at the next daily evaluation. Completing or deleting the milestone, or archiving its campaign, cancels an Outstanding request. A milestone whose date has already passed receives no request.
An incomplete milestone whose date, or whose range end, falls before the current date in the workspace timezone is marked overdue wherever it appears.
RISK5 clauses
An operator can create, edit, and delete a risk. It belongs to exactly one campaign, requires a title and an owner, has a severity of Low, Medium, High, or Critical, has an optional due date, carries a signal and a mitigation as free text of at most 2,000 characters each — the signal being the evidence that raised the risk — and has a workflow state.
An open risk is Watching, In progress, Needs decision, or Escalated. Resolving it requires a resolution note of at least one non-whitespace character and records the operator and the time. Reopening a resolved risk returns it to Watching and keeps the previous resolution in activity.
An operator can escalate an open risk with a reason. In one commit the escalation sets the risk's state to Escalated, records the operator, reason, and time, and applies the escalation policy; the response carries any decision the policy created. Policy: risk.escalation.v1; default: escalating a risk of severity Critical creates one unresolved decision of priority Critical linked to that risk unless an unresolved decision is already linked to it, and notifies the campaign owner. Escalating a lower severity creates no decision.
Escalating, updating, or resolving a risk changes that risk, the campaign activity entry ACT-001 requires, the campaign's derived rollups, and — when risk.escalation.v1 fires — the one decision and one notification it creates. No other record changes; a linked decision changes only through decision.resolution.v1.
As shipped and before any operator edit, Risks Lens contains exactly the open risks of severity High or Critical whose campaign is non-archived and whose launch date is no later than 30 days from today in the workspace timezone, so a campaign already past its launch date stays in the lens. It orders Critical before High, then earliest due date, then risks without a due date, then campaign code, and shows severity, state, owner, due date, signal, mitigation, and campaign.
DEC8 clauses
An operator can create, edit, and delete a decision. It belongs to exactly one campaign, links to at most one risk and to at most one decision it supersedes, requires a title and a requester, and has a priority of Critical, High, Medium, or Low, a due time, a recommendation, an impact statement, an optional assignee, and a state of Awaiting approval, In review, Approved, Rejected, or Cancelled. Awaiting approval and In review are unresolved; the other three are terminal.
Decision Queue contains exactly the unresolved decisions on non-archived campaigns. Policy: decision.queue-order.v1; default order: overdue first, then priority Critical, High, Medium, Low, then earliest due time, then earliest creation time.
Approving an unresolved decision records the approver, the time, the outcome, and an optional rationale, adds a campaign activity entry, applies decision.resolution.v1, and removes the decision from Decision Queue, all in one commit.
A request that approves, rejects, or cancels a decision is a mutation under VIEW-006. The first such outcome to commit is the decision's outcome; a later one against the older version returns 409 and changes nothing.
Any operator can assign or reassign an unresolved decision to a current operator, which records the operator and the time and notifies the new assignee. Assignment changes no other field.
Requesting changes on an unresolved decision requires a rationale of at least one non-whitespace character, records the operator and the time, and sets the decision to In review. Rejecting or cancelling an unresolved decision also requires such a rationale and is a terminal outcome under DEC-004.
A decision outcome changes its linked risk only through the resolution policy, and no other decision action changes a risk. Policy: decision.resolution.v1; default: approving moves a linked risk from Needs decision or Escalated to In progress; requesting changes, rejecting, and cancelling leave it unchanged. No decision outcome resolves a risk.
A terminal decision cannot be edited into another outcome; such a request is rejected and changes nothing. An operator can create a new decision that supersedes it under DEC-001, and each decision links to the other on both details.
LENS7 clauses
A lens has a name, a description, exactly one record type from Campaign, Risk, Milestone, or Decision, at most 20 conditions, a sort order, visible fields, and a display mode valid for its type: Grid for Campaign, Cards for Risk or Decision, Timeline or Agenda for Milestone. A lens with a mode its type does not allow is rejected on save.
Lens preview evaluates the unsaved definition against non-archived records, reports the exact matching count under VIEW-007 within 2 seconds, changes no record, and shows an explicit zero-results state.
Saving a lens makes it available to every operator and records who saved it and when. Sharing produces a URL to the saved lens that requires an operator session; no lens URL is readable without one.
An operator can edit a lens definition or delete the lens. Neither changes, archives, or deletes any record the lens displays or hides. A shipped lens cannot be deleted.
Opening or refreshing a lens re-evaluates it against non-archived records, and its result list, count, condition summary, and selected detail all describe that one evaluation.
Risks Lens and Timeline Lens ship as saved lenses. Operators can duplicate or edit them, and restoring either returns its definition to exactly the one in RISK-005 or MILE-002 without changing a record.
A condition names one field of the lens's record type — campaign custom fields only on a Campaign lens — and one operator from equals, not equals, one of, contains, is empty, is not empty, before, after, and within the next N days, typed to that field: contains applies to text fields only, and the date operators to date fields only. Conditions combine with AND. A condition naming an unknown field, or an operator its field's type does not support, is rejected on preview and on save.
ACT4 clauses
Campaign activity is ordered newest first. It records every create, field change, archive, restore, and delete of the campaign and of its deliverables, milestones, risks, decisions, and comments, plus every import and every mutation made by an automation. Each entry carries its type, its subject's ID, the operator or the automation run that acted, the time, and for a field change the field name with its previous and new value. Campaign activity is the operator-visible view of the audit records in BASE-OPS-005: deleting a campaign removes this view, and those audit records survive with the campaign reference anonymised.
An operator can add a plain-text comment to a campaign, and it appears in campaign activity. A comment changes no field of any record and triggers no automation and no AI call. An operator can delete a comment, which leaves a deletion entry in activity.
Every timestamp in the API and the export is ISO 8601 in UTC. Campaign, deliverable, and milestone dates are calendar dates evaluated from the start of that day in the workspace timezone; decision due times are instants. Every relative time exposes its absolute time as its accessible name.
A comment can mention a current operator, which notifies exactly that operator and changes no field of any record. A mention of a removed or erased operator renders as plain text and notifies nobody.
AUTO11 clauses
An automation has a name, one trigger, an ordered list of actions, an enabled state, and a definition version that increments on every edit. A trigger is a product event declared in seed.json → extensionPoints.events or a schedule of at most one run per hour, and a shorter schedule is rejected on save. An action is one of escalate-risk, create-decision, notify-operator, send-email, request-ai-analysis, and add-activity, plus any command contributed through jobs.commands.v1.
An event automation is dispatched within 10 seconds of the mutation that raised its event committing. A rolled-back mutation dispatches nothing. A disabled automation runs on neither events nor schedules and records no run.
One run exists per automation, definition version, and trigger event or scheduled instant; transport retries are attempts inside that run. Under BASE-OPS-003, a redelivered message reuses each action's idempotency key, so no decision, notification, analysis, or activity entry is created twice.
A run records the definition version that created it. Editing an automation affects only later triggers. Disabling one cancels queued runs that have not started, recording each as Cancelled with no action executed.
Every mutation made by an automation carries the ID of the run that made it, and a run inherits its cause's chain of run IDs. An event whose chain already contains a given automation starts no new run of it, and the suppression is recorded on the causing run. A chain is at most 10 runs deep, and an eleventh is recorded as Suppressed and not dispatched. Two runs triggered by the same event may finish in either order and neither waits for the other.
Every run records its automation version, trigger, subject, queued, started, and finished times, each attempted action with its result, the number of records it affected, and the error code and message of any failure. Run history is retained in full and paged under VIEW-007, and Automation Monitor shows each automation's success rate as finished successful runs divided by finished runs over the trailing 30 days, stating that window.
An action that fails with a transport error is retried at 1, 5, 15, 30, and 60 minutes after its first failure. A retry re-executes only actions that have not completed and reuses their idempotency keys. When the fifth retry fails the run is marked Failed.
A validation error, a policy rejection, a schema failure, or a provider refusal fails the run immediately with no retry. Every Failed run is listed in Automation Monitor with its error code and message under BASE-OPS-004.
An operator can retry a Failed run. The retry is a new run linked to the original by retryOf that reuses the original action idempotency keys, so completed actions do not repeat, and the original run stays Failed and unchanged.
A mutation that changes many records at once — a bulk edit under CAMP-013 or a committed import under IMPORT-003 — dispatches at most one run per enabled automation, carrying the affected record IDs, rather than one run per record.
The seed ships four automations. Critical risk escalation triggers when a risk's severity becomes Critical while the risk is open and not already Escalated, and its one action escalates that risk under RISK-003; it is enabled by default. Launch readiness runs on the schedule in MILE-004 and is enabled by default; its lead time, 5 days by default, and its evaluation hour, 08:00 by default, are editable in settings. Campaign brief triggers when a campaign is created or updated, at most once per campaign per hour, skipped when nothing in its AI-002 context changed since the last brief; it is enabled by default and dispatched only while AI is enabled under SET-002. The weekly portfolio digest runs on the schedule in NOTIFY-004 and is disabled by default. No other parameter of a shipped automation is editable, and each can be disabled.
NOTIFY8 clauses
An operator receives an in-app notification when a decision is assigned to them, when they are mentioned in a comment, when a Critical risk is escalated on a campaign they own, when a readiness request is created for them, and when an automation run they started fails. A Failed run that no operator started notifies the owner of the campaign it acted on, or every operator when the run has no campaign subject.
Every in-app notification has an email counterpart routed by the delivery policy through mail.sender.v1 against the workspace quiet hours in settings, which default to 18:00–08:00 in the workspace timezone. Authentication email and the weekly digest are never held. Policy: notification.delivery.v1; default: a Critical risk escalation or a decision assignment is emailed within 60 seconds at any hour; every other email raised during quiet hours is held and sent within 60 seconds of quiet hours ending, as one message per operator.
Every outbound email has a delivery record linked to its notification and, when an automation sent it, to that run. A failed send, including an asynchronous bounce, is retried on the AUTO-007 schedule and then marked failed on both the delivery record and the notification, where settings lists it. The in-app notification stays available.
When enabled, the weekly digest is sent within 15 minutes of 09:00 each Monday in the workspace timezone to the configured recipients, defaulting to every current operator, and once per recipient address however often that address appears. It itemises non-archived campaigns, launches in the next 30 days, open High and Critical risks, unresolved decisions, and runs that failed in the previous 7 days.
The notification inbox lists an operator's notifications newest first and the navigation shell shows their unread count. A notification is unread for an operator until that operator opens it, an operator can mark one or all of their notifications read, and one operator's read state never changes another's. A notification whose subject has been archived or deleted is shown as unavailable rather than linking to a missing record.
An operator receives at most one in-app notification and one email for a single product event however many of its roles they hold, is never notified of an action they took themselves, and a repeat of the same event on the same subject within 60 minutes updates the existing unread notification instead of creating another.
In settings each operator can mute any notification kind for themselves. Muting stops the email for that kind for that operator only; the in-app notification is still created, and delivery to every other operator is unchanged.
A decision still unresolved 15 minutes after its due time notifies its assignee once, or its requester once when it has no assignee. Moving the due time later re-arms that notification once for the new time.
AI6 clauses
An operator, or the shipped campaign-brief automation, can request a campaign brief, a risk readout, or a lens proposal. Each result is stored as an immutable AI analysis linked to its campaign, to the lens it was invoked from, or to its lens draft, marked as AI-generated wherever it appears, stamped with the time it was produced, and citing the ID and version of every entity in its input.
Policy: ai.campaign-context.v1; default: a campaign brief receives the campaign's standard fields and its linked deliverables, milestones, open risks, and unresolved decisions; a risk readout receives the result set of the lens it was invoked from, capped at 200 risks; a lens proposal receives the supported field definitions and the operator's prompt. Comment text and custom-field values go to none of the three.
AI output never changes a field of any record: it never completes a deliverable, moves a milestone, escalates or resolves a risk, resolves a decision, saves a lens, or sends a notification. A lens proposal stays an editable draft until an operator saves it.
AI calls go through ai.provider.v1 with a 60-second timeout. A timeout, a response that fails its schema, or an unconfigured or unavailable provider stores no analysis and changes no field of any record. An operator-initiated request shows the provider's error code and message where it was requested; an automation-initiated one fails that run under AUTO-008. Every other automation, notification, mutation, and read proceeds unaffected.
Every AI call records its feature, model, input and output token counts, reported cost, subject, requesting operator or automation run, duration, and result. Settings shows call counts, token counts, and cost by day and by feature.
Settings holds a daily ceiling on AI calls, 200 by default, resetting at 00:00 in the workspace timezone. A call that would exceed it is refused with an error naming the ceiling, stores no analysis, and fails any run that requested it under AUTO-008. Non-AI work is unaffected.
IMPORT5 clauses
An operator can import a campaign, deliverable, or milestone CSV within the upload limits under BASE-INPUT-003 and the row limit in seed.json → limits. The file must be UTF-8, comma-delimited, and carry a header row; a byte-order mark is accepted and anything else is rejected with a parse error. Import requires an explicit mapping from columns to fields and, before any record changes and within 60 seconds at the row limit, shows the number of creates, updates, and unchanged rows, every row error, and for each updated row the fields that will change with their stored and incoming values.
A campaign row creates or updates a campaign by its unique code; a deliverable or milestone row creates or updates by campaign code plus title, and is a row error when that campaign is not already in the workspace. A merge key repeated inside the file, and a reference to an owner or select value that does not exist, are row errors. A value that is empty or only whitespace never overwrites a stored value.
Confirm is available only when every row is valid; one invalid row blocks the whole file and there is no partial import. Confirming commits every previewed row in one transaction, records an import record, and writes one activity entry on every campaign it created, updated, or whose deliverables or milestones it changed; any failure rolls back every row. A committed import cannot be rolled back, which is why IMPORT-001 shows every field it will change.
Preview returns a token that expires 30 minutes after it is issued and that confirm requires, together with an idempotency key. A record matched by the preview that changed before confirm makes confirm return 409 and require a new preview. Replaying a confirm with the same key within 24 hours returns the original result and creates no second record and no second activity entry.
The uploaded file is stored with its import record and is downloadable by any operator. Deleting the import record deletes the file within 5 minutes.
SET3 clauses
Settings is one operator-only area with sections for the workspace (name, timezone, currency, quiet hours), operators, notification preferences, custom-field definitions, automations and digest recipients, AI, imports and failed email deliveries, and current usage against every limit in seed.json → limits. Changing a setting records the operator and the time and applies to work dispatched after the change; work already dispatched is unaffected.
AI is off until an operator enables it in settings while the configured ai.provider.v1 adapter has every environment value in seed.json → env. While AI is off, an AI request is rejected with a message naming what is missing, the campaign-brief automation is not dispatched, and every other feature works unchanged.
A new deployment starts with the operator configured at setup, the configured timezone and currency, the two shipped lenses, the four shipped automations, and no campaigns, and every surface shows the empty state in VIEW-004. An operator can install and remove sample data in one action from settings; removal deletes only records the sample created and is refused, naming them, once any of those records has been edited.
DATA3 clauses
In addition to the archive guarantees in BASE-DATA-001, the export contains every workspace setting, operator, campaign, tag, custom-field definition and value, deliverable, milestone and dependency, risk, decision and supersede link, lens, comment, activity entry, notification, delivery record, automation definition and run, import record, AI analysis, and AI usage record as JSON, and every stored import file in its original form.
An operator can export the campaign grid or any lens or queue result as CSV containing exactly the visible fields in their displayed order and exactly the records the current conditions select, with dates in the workspace timezone and money in the workspace currency, naming the view, the currency, and the instant it was taken. It is a one-way download and creates no sync relationship with any external system.
A create, invitation, or import that would take the workspace past a limit in seed.json → limits is rejected with 409 naming the limit, its value, and the current count, and changes nothing. No record, row, or field is silently dropped or trimmed.
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.
- v0.0.0-bootstrap
- Current — ui-ported-thin-data-spine
The manifest states this version and maturity. Consult the source verification evidence before deployment.
DIY quickstart
The code is MIT and the deployment is yours. Nothing below talks to us; optional management remains planned.
This edition has not published a source README yet; use its manifest and contract for current scope and status.
View as agent
Everything on this page exists as machine-readable files. If you are an agent evaluating Cradle for someone, read these instead of the prose.
No catalogue install command or source README is declared for this edition.
- seed.json
- The manifest: capabilities mapped to clauses, externals, extension points, limits, operating cost, deploy requirements.
- CONTRACT.md
- The 97 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 Cradle on your own cloud.
MIT code, your data, your infrastructure. Leave whenever you like and it keeps running.