Retention Policy Check
governance.retention_policy_check
Overview
You send one object's metadata — its timestamps, classification, legal-hold flag and attributes — plus your retention clauses and the instant to judge them at, and we return the action, the due instant, and the clause that decided. Calendar durations add in the zone you declare, so a year is a year and not 365 days, and month arithmetic clamps to the last valid day. A legal hold short-circuits every clause. Where two clauses compete, the published order is highest precedence, then the more conservative action, then the longer duration, then the smallest id — and every clause that order discarded comes back so you can see the delete rule that lost. We recommend; we delete nothing, hold nothing and never look at the object itself.
Retention arithmetic is where records management quietly goes wrong: a year added as 365 days, a month that runs off the end of February, a delete clause that beat a legal hold, or a due date nobody can reproduce because it came from a server's clock. You get the arithmetic, the clause that decided, and the clauses that lost.
Reliability
Fully deterministic and independent of the machine: no clock is read, and the same request gives the same answer on any host, in any default zone or locale. Every window is half-open, so the action fires exactly when asOf reaches dueAt, not a second later. Day-and-above duration components add as calendar amounts in your declared zone while PT components add on the exact instant timeline, which is why P1D and PT24H differ across a daylight-saving transition — that difference is deliberate, and it is why a Y, M, W or D duration needs a zone. A legal hold is answered before any clause is consulted, so there is no predicate for it to test: metadata.legalHold true returns LEGAL_HOLD_BLOCKED whatever your clauses say. Refusals, and what to do about each. GOV_RETENTION_INPUT_TOO_LARGE means the declared request is past the 65,536-byte inline limit, so send fewer clauses or shorter attributes. GOV_RETENTION_METADATA_INVALID means a metadata field is the wrong shape or past its length — a non-scalar attribute value, an attribute string over 256 characters, an empty timestamps object — and the message names the path. GOV_RETENTION_FIELD_UNKNOWN means a property we do not declare turned up, which we refuse rather than ignore so a stray key can never carry a credential in, so delete it. GOV_RETENTION_POLICY_INVALID means a clause is the wrong shape, such as a missing retention block or a non-string predicate value, so fix the named path. GOV_RETENTION_CLAUSE_ID_DUPLICATE means two clauses share an id, which would make the receipt ambiguous, so make them unique. GOV_RETENTION_CLAUSE_LIMIT_EXCEEDED means more than 100 clauses arrived, so split the policy. GOV_RETENTION_PREDICATE_LIMIT_EXCEEDED means one clause carries more than 16 predicates, so split that clause in two. GOV_RETENTION_PREDICATE_UNSUPPORTED means a predicate name is not one of the three, so correct the name — we accept no regular expression or glob as a predicate, and there is no legal-hold predicate because a hold is answered before matching. GOV_RETENTION_ACTION_UNSUPPORTED means a clause action or your defaultAction is not RETAIN, REVIEW or DELETE; LEGAL_HOLD_BLOCKED is ours to produce and cannot be declared. GOV_RETENTION_MEASURED_FROM_UNSUPPORTED means measuredFrom is not one of the five timestamp names, so pick one of them. GOV_RETENTION_TIMESTAMP_MISSING means a matching clause measures from a timestamp your metadata does not carry, and we refuse rather than fall back to createdAt or skip the clause, so supply that timestamp. GOV_RETENTION_TIMESTAMP_INVALID means a timestamp is not an ISO-8601 instant with an explicit offset, and a local time is never assumed to be UTC, so add the offset. GOV_RETENTION_DURATION_INVALID means a duration is not ISO-8601, is negative, or exceeds the P36500D equivalent, so correct it. GOV_RETENTION_ASOF_INVALID means asOf is missing its offset or is not an instant we can parse, so send the full form. GOV_RETENTION_TIMEZONE_REQUIRED means a clause duration carries a Y, M, W or D component but no timeZone came with it, and calendar addition depends on the zone, so declare one. GOV_RETENTION_TIMEZONE_UNKNOWN means the zone id is not one the platform knows, so use an IANA name such as Europe/Amsterdam rather than an abbreviation. GOV_RETENTION_WORK_REFUSED means the projected predicate evaluations went past 1600, and the refusal reports both the projection and the budget, so reduce clauses or predicates per clause.
Context footprint
One action plus a bounded receipt: at most 64 reason entries and one entry per competing clause. Your policy is never echoed back. A successful call costs 1 execution credit.
When to use it
When you hold a retention policy and one object's metadata and need the action, the due instant and the clause behind them at a specific moment.
When you want the deletion carried out, when you need working-day deadlines, or when you have no policy to send and expect us to supply one.
Scope
Non-goals
- Does not delete, archive, expire or move anything
- Does not store, publish or version your retention policy
- Does not read the object, its content or any storage system
- Does not read a clock — the answer is computed at the asOf you supply
- Does not certify compliance with any records-management standard
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill only accepts structured technical values such as numbers, booleans, enums, timestamps, or identifiers. It does not process free-form text, files, or documents.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response does not contain submitted content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| metadata | object | Yes | Closed object metadata for retention arithmetic: required timestamps plus optional id, classification, legal hold, and scalar attributes. |
| policy | object | Yes | Closed retention policy with 1 to 100 clauses, a required default action, and an optional IANA zone for calendar durations. |
| evaluation | object | Yes | Closed evaluation clock with the asOf instant the answer is computed at, because no server clock is read. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| action | string | Yes | Recommended action now: RETAIN, REVIEW, DELETE, or LEGAL_HOLD_BLOCKED. |
| dueAt | string | No | Deciding clause's due instant; omitted when a legal hold blocked or the default action applied. |
| reasons | array | Yes | Arithmetic receipt capped at 64 entries, each with a stable code and optional clause fields. |
| decidingClauseId | string | No | Clause the precedence order selected; omitted when default or legal hold decided. |
| competingClauses | array | Yes | Matching clauses discarded by precedence, empty when only one clause matched. |
| summary | object | Yes | Closed retention counters including optional objectId and timeZone, clause counts, asOf, legal-hold flag, default-applied flag, and truncation. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"governance.retention_policy_check","input":{"metadata":{"timestamps":{"createdAt":"2018-03-01T09:00:00+01:00"}},"policy":{"clauses":[{"id":"finance-7y","action":"DELETE","retention":{"measuredFrom":"CREATED_AT","duration":"P7Y"}}],"defaultAction":"RETAIN","timeZone":"Europe/Amsterdam"},"evaluation":{"asOf":"2026-07-29T12:00:00+02:00"}}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("governance.retention_policy_check", {"policy":{"timeZone":"Europe/Amsterdam","defaultAction":"RETAIN","clauses":[{"id":"finance-7y","match":[{"values":["financial-record"],"predicate":"classificationIn"}],"precedence":100,"action":"DELETE","retention":{"measuredFrom":"CREATED_AT","duration":"P7Y"}}]},"metadata":{"classification":"financial-record","timestamps":{"createdAt":"2018-03-01T09:00:00+01:00"},"objectId":"invoice-2018-0042"},"evaluation":{"asOf":"2026-07-29T12:00:00+02:00"}});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| GOV_RETENTION_INPUT_TOO_LARGE | 429 | No | A size, quota, rate, or time limit was exceeded. |
| GOV_RETENTION_METADATA_INVALID | 400 | No | The request payload failed this skill's input validation. |
| GOV_RETENTION_FIELD_UNKNOWN | 400 | No | The request payload failed this skill's input validation. |
| GOV_RETENTION_POLICY_INVALID | 400 | No | The request payload failed this skill's input validation. |
| GOV_RETENTION_CLAUSE_ID_DUPLICATE | 400 | No | The request payload failed this skill's input validation. |
| GOV_RETENTION_CLAUSE_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| GOV_RETENTION_PREDICATE_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| GOV_RETENTION_PREDICATE_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| GOV_RETENTION_ACTION_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| GOV_RETENTION_MEASURED_FROM_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| GOV_RETENTION_TIMESTAMP_MISSING | 400 | No | The request payload failed this skill's input validation. |
| GOV_RETENTION_TIMESTAMP_INVALID | 400 | No | The request payload failed this skill's input validation. |
| GOV_RETENTION_DURATION_INVALID | 400 | No | The request payload failed this skill's input validation. |
| GOV_RETENTION_ASOF_INVALID | 400 | No | The request payload failed this skill's input validation. |
| GOV_RETENTION_TIMEZONE_REQUIRED | 400 | No | The request payload failed this skill's input validation. |
| GOV_RETENTION_TIMEZONE_UNKNOWN | 400 | No | The request payload failed this skill's input validation. |
| GOV_RETENTION_WORK_REFUSED | 429 | No | A size, quota, rate, or time limit was exceeded. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Does not delete, archive, expire or move anything
- Does not store, publish or version your retention policy
- Does not read the object, its content or any storage system
- Does not read a clock — the answer is computed at the asOf you supply
- Does not certify compliance with any records-management standard
- Performing the deletion itself
- Judging thousands of objects in one call — this answers for one object
- Working-time or holiday-aware deadlines, which are a different calculation
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "governance.retention_policy_check",
"input": {
"policy": {
"timeZone": "Europe/Amsterdam",
"defaultAction": "RETAIN",
"clauses": [
{
"id": "finance-7y",
"match": [
{
"values": [
"financial-record"
],
"predicate": "classificationIn"
}
],
"precedence": 100,
"action": "DELETE",
"retention": {
"measuredFrom": "CREATED_AT",
"duration": "P7Y"
}
}
]
},
"metadata": {
"classification": "financial-record",
"timestamps": {
"createdAt": "2018-03-01T09:00:00+01:00"
},
"objectId": "invoice-2018-0042"
},
"evaluation": {
"asOf": "2026-07-29T12:00:00+02:00"
}
}
}Examples
Seven-year ledger clause already past its due instant
{
"policy": {
"timeZone": "Europe/Amsterdam",
"defaultAction": "RETAIN",
"clauses": [
{
"id": "finance-7y",
"match": [
{
"values": [
"financial-record"
],
"predicate": "classificationIn"
}
],
"precedence": 100,
"action": "DELETE",
"retention": {
"measuredFrom": "CREATED_AT",
"duration": "P7Y"
}
}
]
},
"metadata": {
"classification": "financial-record",
"timestamps": {
"createdAt": "2018-03-01T09:00:00+01:00"
},
"objectId": "invoice-2018-0042"
},
"evaluation": {
"asOf": "2026-07-29T12:00:00+02:00"
}
}{
"dueAt": "2025-03-01T09:00:00+01:00",
"reasons": [
{
"code": "RETENTION_CLAUSE_DUE",
"dueAt": "2025-03-01T09:00:00+01:00",
"measuredFrom": "CREATED_AT",
"clauseId": "finance-7y"
}
],
"competingClauses": [],
"action": "DELETE",
"decidingClauseId": "finance-7y",
"summary": {
"objectId": "invoice-2018-0042",
"clauseCount": 1,
"matchedClauseCount": 1,
"asOf": "2026-07-29T12:00:00+02:00",
"timeZone": "Europe/Amsterdam",
"legalHold": false,
"defaultApplied": false,
"truncated": false
}
}Legal hold blocks a top-precedence delete clause
{
"policy": {
"defaultAction": "REVIEW",
"clauses": [
{
"action": "DELETE",
"retention": {
"measuredFrom": "CLOSED_AT",
"duration": "PT17520H"
},
"precedence": 1000,
"id": "closed-cases-2y"
}
]
},
"metadata": {
"timestamps": {
"closedAt": "2021-11-30T17:00:00Z"
},
"objectId": "case-file-8891",
"legalHold": true
},
"evaluation": {
"asOf": "2026-07-29T12:00:00Z"
}
}{
"action": "LEGAL_HOLD_BLOCKED",
"reasons": [
{
"code": "RETENTION_LEGAL_HOLD_ACTIVE"
}
],
"competingClauses": [],
"summary": {
"objectId": "case-file-8891",
"clauseCount": 1,
"matchedClauseCount": 0,
"asOf": "2026-07-29T12:00:00Z",
"legalHold": true,
"defaultApplied": false,
"truncated": false
}
}Recommended use cases
- Decide whether one record is past its retention boundary at a given instant
- Show a reviewer which clause decided, and which competing clause was out-ranked
- Prove a legal hold beats every clause, whatever precedence it carries
Avoid for
- Performing the deletion itself
- Judging thousands of objects in one call — this answers for one object
- Working-time or holiday-aware deadlines, which are a different calculation
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.