Orient yourself first
npm run crm -- app inspect --json
Read valid, then problems[], then limitations[], in that order. Every problem is fixed or reported before anything is built on top of it, and every limitation is a hard boundary on what you may claim. Then read packages[], capabilities[], resources[], actions[], policies[] and providers[]: that list is what exists. A capability absent from the report does not exist, whatever a record name, a label or a document suggests.
If the repository documents this skill names are absent, you are in a project built from this framework rather than in the framework itself. The inspection report is then the source of truth and those documents are optional background — do not guess at their contents, and do not assume a path exists because this skill names it.
Background, where they exist: ARCHITECTURE.md, DECISIONS.md (ADR-018 and its addenda, ADR-019 with addendum 1), docs/SERVICE_OPERATIONS.md, docs/MODULE_EVOLUTION.md and docs/PACKAGE_AUTHORING.md. They are the deeper source for the rules below, not a prerequisite for them — the rules stand on their own.
It is not a contract, and the name is the guardrail
- A ServiceCoverage is operational evidence. Nobody signed it, it has no envelope, signer or artifact, and it neither amends nor replaces the Commercial Contract it was activated from. Every document, every action description and every screen says so before it says anything else.
- Never introduce a record, field or label called service contract. Anything called Contract here is read as signed, and then two records claim to be authoritative.
activate-service returns amendedCommercialRecord: false because that is the claim a reader would otherwise make. Keep claims like it, and keep them honest.
Reach the sale only through the capability
- Service depends on
contracts/service-obligations@1 and on nothing else in that package: no import from packages/contracts/src, no table, no source key. If the capability lacks something, add it inside packages/contracts, never in the kernel.
- Open it with your own
modules, actor and now, so the obligation update commits inside your transaction. An unawaited managed write would escape it — await every one.
- Consume exactly the obligations you planned, by id. Delivery obligations are not Service's business: never touch them.
- Evolving a shipped contracts record is ADR-019 work: bump
revision, one step, additive. Prove it against a project that already holds real rows — an evolution that only works on an empty table is not an evolution.
Decide an entitlement, never default one
- A service activation policy is versioned, fingerprinted and pure. It reads the obligation and the contract and returns a support tier, covered categories and priorities, elapsed-minute targets and an optional open-case limit — or
ambiguous.
ambiguous blocks activation until a human supplies an override with a stated reason. A defaulted support tier is a promise nobody in the business made.
activate-service recomputes the plan server-side. A caller may choose the policy and resolve an ambiguity; it never supplies the outcome.
plan-service-activation is read-only and open to an agent: no record, no audit row, no event. Keep it that way, and test the zero counts rather than asserting them in prose.
Enforce entitlements on the server
- Category, priority, coverage state, effective dates and the open-case limit are all checked in the action. None of it may depend on the UI having filtered anything.
- Count the open-case limit with an unpaged read (
listWhere/countWhere). A limit computed from a page bound lets the eleventh case through on a project with a large history.
- Exceeding a limit refuses the case. Nothing is billed for exceeding it, because nothing here bills.
SLA is elapsed time, and says so
firstResponseDueAt = openedAt + firstResponseTargetMinutes, resolutionDueAt = openedAt + resolutionTargetMinutes. Elapsed wall-clock minutes against the injected UTC clock.
- There is no business-hours calendar, no holiday table, no timezone interpretation and no paused clock:
waiting_customer does not stop it. A half-implemented pause understates a breach, and that is the direction that hurts.
breached is an elapsed-time state, never a contractual or legal determination. Never write "contractually breached", and never emit an event that says it.
preview-sla computes and writes nothing. record-sla-evaluation stores the judgement with every input it used, so a stored evaluation is evidence about a stated instant and never current truth.
A case is evidence, not authentication
- Every case, note and reply is what a user actor recorded. No customer is authenticated, no channel is integrated, and nothing is emailed, called or messaged. The
source field is a label; a listed channel does not mean a provider is connected.
visibility is an assertion by whoever recorded it, never an access grant.
- An escalation is recorded evidence that a human escalated. Nothing is routed, notified, assigned or scheduled, so a target is a label rather than a permission.
- Moves follow the declared transition table and nothing else. Nothing moves on a clock.
Keys, and the collisions they must not have
- A caller's key and a key the framework writes itself must live in separate namespaces.
sourceKey is unique, and evidence records are append-only: a caller who seizes the key an action owns blocks that action permanently, with no correction path.
- Framework evidence for a repeatable move needs an occurrence ordinal, not just
<from>:<to>. in_progress ↔ waiting_customer is the ordinary support loop, and a key that repeats collides with its own earlier evidence and strands the case.
- A retry answers from storage only after proving it is the same call — including the policy the caller stated. A divergent retry reported as honoured is worse than a refusal.
Human-only writes
Activating, ending, recording a case, a first response, a transition, an activity, an SLA evaluation and an escalation each require actor.type === 'user'; an agent is refused 403 HUMAN_APPROVAL_REQUIRED, and the refusal writes nothing. This is a human-actor boundary, not Service Manager or customer role enforcement — there are no roles to enforce until the Production Spine exists. Say that wherever the boundary appears.
The Admin section
apps/admin/public/admin-service.js is package-scoped, not package-owned: AX1 publishes ADMIN_EXTENSIONS_UNSUPPORTED, so it lives in the Admin app and renders only while /api/schema publishes domains.service. A control appears only where the server would accept it, every refusal re-throws so the parent's re-render cannot paint over it, and selection that must survive a write lives outside the render closure.
Prove it, do not argue it
Fault injection after every write in each action's write graph · two connections racing every decision, including the same move · exact reads past 500 rows · exact audit, event and trace counts · the SLA arithmetic at its exact boundaries · hostile input in every field · AX1 visibility, AX2 citability and a clean detach · a live upgrade over data that predates the change. Finish with npm run verify.
Never build here
Billing, invoicing, payment or billing eligibility · a second legal contract · contract amendment, renewal or expansion — those exist since M16b, in the contracts and lifecycle packages, and never here · an authenticated customer or agent portal · RBAC or tenancy · email, chat, WhatsApp or telephony · a contact centre · attachment or file storage · a knowledge base · business-hours calendars · a scheduler, automatic escalation or notification · customer-success health scoring.
1---2name: build-service-operations3description: Add or extend service operations in an Accordo project - activating the pending service obligations of an activated contract into an operational Service Coverage with immutable Entitlements through a versioned activation policy, and recording support cases over an explicit transition table with elapsed-time SLA evidence and manually recorded escalation. Use for service coverage, support entitlement, support case/ticket, first response, SLA target or evaluation, case escalation, ending coverage, or the Service Admin section. Do not use for a second legal contract, billing, invoicing or an authenticated customer portal (none of which exist), renewal or amendment execution (build-contract-activation), contract activation (build-contract-activation), delivery work (build-delivery-handover) or a single custom object (create-crm-module).4---56## Orient yourself first78```bash9npm run crm -- app inspect --json10```1112Read `valid`, then `problems[]`, then `limitations[]`, in that order. Every problem is fixed or reported before anything is built on top of it, and **every limitation is a hard boundary on what you may claim.** Then read `packages[]`, `capabilities[]`, `resources[]`, `actions[]`, `policies[]` and `providers[]`: that list is what exists. A capability absent from the report does not exist, whatever a record name, a label or a document suggests.1314If the repository documents this skill names are absent, you are in a project built from this framework rather than in the framework itself. The inspection report is then the source of truth and those documents are optional background — do not guess at their contents, and do not assume a path exists because this skill names it.1516**Background, where they exist:** `ARCHITECTURE.md`, `DECISIONS.md` (ADR-018 and its addenda, ADR-019 with addendum 1), `docs/SERVICE_OPERATIONS.md`, `docs/MODULE_EVOLUTION.md` and `docs/PACKAGE_AUTHORING.md`. They are the deeper source for the rules below, not a prerequisite for them — the rules stand on their own.1718## It is not a contract, and the name is the guardrail19201. A **ServiceCoverage** is operational evidence. Nobody signed it, it has no envelope, signer or artifact, and it neither amends nor replaces the Commercial Contract it was activated from. Every document, every action description and every screen says so before it says anything else.212. Never introduce a record, field or label called *service contract*. Anything called *Contract* here is read as signed, and then two records claim to be authoritative.223. `activate-service` returns `amendedCommercialRecord: false` because that is the claim a reader would otherwise make. Keep claims like it, and keep them honest.2324## Reach the sale only through the capability25261. Service depends on `contracts/service-obligations@1` and on nothing else in that package: no import from `packages/contracts/src`, no table, no source key. If the capability lacks something, add it **inside `packages/contracts`**, never in the kernel.272. Open it with your own `modules`, `actor` and `now`, so the obligation update commits inside your transaction. An unawaited managed write would escape it — await every one.283. Consume exactly the obligations you planned, by id. Delivery obligations are not Service's business: never touch them.294. Evolving a shipped contracts record is ADR-019 work: bump `revision`, one step, additive. Prove it against a project that already holds real rows — an evolution that only works on an empty table is not an evolution.3031## Decide an entitlement, never default one32331. A **service activation policy** is versioned, fingerprinted and pure. It reads the obligation and the contract and returns a support tier, covered categories and priorities, elapsed-minute targets and an optional open-case limit — or `ambiguous`.342. `ambiguous` blocks activation until a human supplies an override **with a stated reason**. A defaulted support tier is a promise nobody in the business made.353. `activate-service` recomputes the plan server-side. A caller may choose the policy and resolve an ambiguity; it never supplies the outcome.364. `plan-service-activation` is read-only and open to an agent: no record, no audit row, no event. Keep it that way, and test the zero counts rather than asserting them in prose.3738## Enforce entitlements on the server39401. Category, priority, coverage state, effective dates and the open-case limit are all checked in the action. None of it may depend on the UI having filtered anything.412. Count the open-case limit with an **unpaged** read (`listWhere`/`countWhere`). A limit computed from a page bound lets the eleventh case through on a project with a large history.423. Exceeding a limit **refuses the case**. Nothing is billed for exceeding it, because nothing here bills.4344## SLA is elapsed time, and says so45461. `firstResponseDueAt = openedAt + firstResponseTargetMinutes`, `resolutionDueAt = openedAt + resolutionTargetMinutes`. Elapsed wall-clock minutes against the injected UTC clock.472. There is no business-hours calendar, no holiday table, no timezone interpretation and **no paused clock**: `waiting_customer` does not stop it. A half-implemented pause understates a breach, and that is the direction that hurts.483. `breached` is an elapsed-time state, never a contractual or legal determination. Never write "contractually breached", and never emit an event that says it.494. `preview-sla` computes and writes nothing. `record-sla-evaluation` stores the judgement **with every input it used**, so a stored evaluation is evidence about a stated instant and never current truth.5051## A case is evidence, not authentication52531. Every case, note and reply is what a **user actor** recorded. No customer is authenticated, no channel is integrated, and nothing is emailed, called or messaged. The `source` field is a label; a listed channel does not mean a provider is connected.542. `visibility` is an assertion by whoever recorded it, never an access grant.553. An escalation is recorded evidence that a human escalated. Nothing is routed, notified, assigned or scheduled, so a target is a label rather than a permission.564. Moves follow the declared transition table and nothing else. Nothing moves on a clock.5758## Keys, and the collisions they must not have59601. A caller's key and a key the framework writes itself must live in **separate namespaces**. `sourceKey` is unique, and evidence records are append-only: a caller who seizes the key an action owns blocks that action permanently, with no correction path.612. Framework evidence for a repeatable move needs an **occurrence ordinal**, not just `<from>:<to>`. `in_progress ↔ waiting_customer` is the ordinary support loop, and a key that repeats collides with its own earlier evidence and strands the case.623. A retry answers from storage only after proving it is the same call — including the policy the caller *stated*. A divergent retry reported as honoured is worse than a refusal.6364## Human-only writes6566Activating, ending, recording a case, a first response, a transition, an activity, an SLA evaluation and an escalation each require `actor.type === 'user'`; an agent is refused `403 HUMAN_APPROVAL_REQUIRED`, and the refusal writes nothing. This is a **human-actor boundary, not Service Manager or customer role enforcement** — there are no roles to enforce until the Production Spine exists. Say that wherever the boundary appears.6768## The Admin section6970`apps/admin/public/admin-service.js` is **package-scoped, not package-owned**: AX1 publishes `ADMIN_EXTENSIONS_UNSUPPORTED`, so it lives in the Admin app and renders only while `/api/schema` publishes `domains.service`. A control appears only where the server would accept it, every refusal re-throws so the parent's re-render cannot paint over it, and selection that must survive a write lives outside the render closure.7172## Prove it, do not argue it7374Fault injection after every write in each action's write graph · two connections racing every decision, including the *same* move · exact reads past 500 rows · exact audit, event and trace counts · the SLA arithmetic at its exact boundaries · hostile input in every field · AX1 visibility, AX2 citability and a clean detach · a live upgrade over data that predates the change. Finish with `npm run verify`.7576## Never build here7778Billing, invoicing, payment or billing eligibility · a second legal contract · contract amendment, renewal or expansion — those exist since M16b, in the contracts and lifecycle packages, and never here · an authenticated customer or agent portal · RBAC or tenancy · email, chat, WhatsApp or telephony · a contact centre · attachment or file storage · a knowledge base · business-hours calendars · a scheduler, automatic escalation or notification · customer-success health scoring.