@agentsfleet/orly 0.14.6 → 0.14.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -78,7 +78,7 @@ TypeSafe's Jev model supplies runtime advice through explicit `orly judge` reque
78
78
 
79
79
  See [releases](https://github.com/agentsfleet/orly/releases) for published versions.
80
80
  The install commands below fetch the published package.
81
- The release workflow publishes 0.14.6 after its version change merges to `main`.
81
+ The release workflow publishes 0.14.7 after its version change merges to `main`.
82
82
 
83
83
  ---
84
84
 
package/docs/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.14.7 — 2026-10-17
4
+
5
+ API instants become RFC 3339 strings, and agentsfleet's schema stores them as `TIMESTAMPTZ(3)` under a clock rule that reads the time after a lock wait.
6
+
7
+ - **REST guide** — `_at` fields are RFC 3339 strings in UTC, OpenAPI `format: date-time`; epoch integers no longer represent instants, and filters state their boundaries.
8
+ - **Schema conventions** — timestamps are `TIMESTAMPTZ(3)`; lifecycle stamps come from `now()`, locking validity checks lock first and then read `clock_timestamp()`, and sweeps compare against `statement_timestamp()`.
9
+
3
10
  ## 0.14.6 — 2026-10-17
4
11
 
5
12
  agentsfleet's schema conventions no longer require migrate to refuse an older migrator's ledger.
@@ -35,7 +35,7 @@ exception; an unchecked item cannot silently become a pass.
35
35
  - [ ] **List envelope** is exactly `{items, total: int|null, next_cursor: string|null}` — no synonyms (§3)
36
36
  - [ ] **Bulk endpoints** use `207` with the canonical per-item shape (§3)
37
37
  - [ ] **Null vs omit** — absent optionals omitted, `null` reserved for "explicitly cleared" (§3)
38
- - [ ] **Datetime fields** are int64 epoch ms with `_at` suffix (NOT ISO 8601) (§4)
38
+ - [ ] **Datetime fields** are RFC 3339 strings in UTC (`Z`) with `_at` suffix; no epoch integers (§4)
39
39
  - [ ] **Duration fields** are integers with `_ms` or `_seconds` suffix; no bare `timeout`/`ttl`/`duration` (§4)
40
40
  - [ ] **Status codes** — 409 includes `current_state`; 412 includes `etag`; 429 includes `Retry-After` + `X-RateLimit-*` (§4)
41
41
  - [ ] **ETag/`If-Match`** wired for any resource with realistic concurrent edits (§4)
@@ -120,7 +120,7 @@ If the parent ID is in the path, never repeat it in the body.
120
120
 
121
121
  - **Plural for collections, singular for items**
122
122
  - **`_id` suffix** for identifiers (`product_id`, `agent_id`) — never bare `id` except as the resource's own primary key field
123
- - **`_at` suffix** for datetimes; values are int64 epoch milliseconds (§4)
123
+ - **`_at` suffix** for datetimes; values are RFC 3339 strings in UTC (§4)
124
124
  - **Unit suffix** for durations: `_ms` or `_seconds` (§4) — never bare `timeout`/`ttl`/`interval`/`duration`/`expiration`
125
125
  - **Adjectives before nouns** (`completed_items`, not `items_completed`)
126
126
  - **No `is_` prefix** on booleans (`enabled`, not `is_enabled`)
@@ -173,7 +173,7 @@ Endpoints that can't complete inside the request window MUST follow this convent
173
173
  1. The kicking POST returns `202 Accepted` with `Location: /v1/operations/{operation_id}` and a body containing `operation_id` and `status: "pending"`.
174
174
  2. `GET /v1/operations/{operation_id}` returns:
175
175
  ```json
176
- { "operation_id": "...", "done": false, "status": "running", "started_at": 1735689600000 }
176
+ { "operation_id": "...", "done": false, "status": "running", "started_at": "2026-10-17T18:06:49.123Z" }
177
177
  ```
178
178
  When `done: true`, the body carries either `result` (the final resource, inline) or `error` (an RFC 7807 envelope per §5).
179
179
  3. Terminal states are `succeeded`, `failed`, `cancelled`. No others.
@@ -213,7 +213,7 @@ The resource itself, no envelope:
213
213
  {
214
214
  "id": "01HZQ...",
215
215
  "name": "platform-ops",
216
- "created_at": 1735689600000
216
+ "created_at": "2026-10-17T18:06:49.123Z"
217
217
  }
218
218
  ```
219
219
 
@@ -227,7 +227,7 @@ GET /products?status=active&sort=-created_at&starting_after=01HZQ...&limit=50
227
227
 
228
228
  - **Filtering grammar — exactly one form per case:**
229
229
  - **Equality:** `?status=active` (single value), `?status=active,paused` (multi-value, comma-separated, single key).
230
- - **Time ranges:** `?created_after=<ts_ms>&created_before=<ts_ms>`. Bracket grammar (`?created_at[gte]=...`) is forbidden.
230
+ - **Time ranges:** `?created_after=<RFC 3339>&created_before=<RFC 3339>`, both exclusive; an endpoint that differs states its boundaries in the parameter description. Bracket grammar (`?created_at[gte]=...`) is forbidden.
231
231
  - **No boolean explosions.** Don't add `?include_x=true&include_y=true` — use `?include=x,y` with a documented enum of legal values, OR don't expose a knob.
232
232
  - **Sorting:** `sort=field` ascending; `sort=-field` descending. Single sort key per request — no multi-key.
233
233
  - **Pagination — keyset.** Request: `?starting_after=<resource_id>&limit=<int>`;
@@ -318,13 +318,22 @@ If you find yourself wanting another status code, check RFC 7231/7807 — don't
318
318
 
319
319
  ### Datetime fields
320
320
 
321
- **Integer milliseconds since Unix epoch (UTC), serialized as JSON number, OpenAPI `type: integer, format: int64`.** Field name uses the `_at` suffix.
321
+ **RFC 3339 strings, serialized as JSON strings, OpenAPI `type: string, format: date-time`.** Field name uses the `_at` suffix.
322
322
 
323
323
  ```json
324
- "created_at": 1735689600000
324
+ "created_at": "2026-10-17T18:06:49.123Z"
325
325
  ```
326
326
 
327
- NOT ISO 8601 strings, NOT seconds, NOT floats, NOT a string-encoded integer. This matches the codebase (`EXTRACT(EPOCH FROM created_at) * 1000` in handlers; `i64` in response structs; `format: int64` in `components/schemas.yaml`). Anyone proposing ISO 8601 string datetimes for a new endpoint is wrong — match the existing convention or amend this rule first.
327
+ 1. Responses MUST emit UTC with the `Z` designator.
328
+ 2. Requests MAY carry any valid numeric UTC offset. The server MUST normalize an accepted instant to UTC.
329
+ 3. Epoch integers MUST NOT represent an instant.
330
+ 4. An invalid or out-of-range instant MUST return `400` with a field-specific `detail` (§5).
331
+ 5. Timestamp filters MUST document whether each boundary is inclusive or exclusive. An opaque pagination cursor MUST carry the complete ordering key at the database's precision.
332
+ 6. Shared validation MUST enforce the documented input range and precision.
333
+
334
+ <!-- oracle-packs:start product.agentsfleet -->
335
+ For `agentsfleet`, wire types hold `jiff::Timestamp` and use its standard serialization; no handler converts an instant by hand. Postgres stores instants as `TIMESTAMPTZ(3)`, so the API, the cursor and the database agree on millisecond precision.
336
+ <!-- oracle-packs:end -->
328
337
 
329
338
  When the field has not yet been set (e.g. an unrevoked key's `revoked_at`), serialize as JSON `null` and declare `nullable: true` in the schema.
330
339
 
@@ -7,14 +7,14 @@ regenerates this file and fails on any difference. To update it, run
7
7
  | Rule document | enforced by | judged | acknowledged | classified | trigger |
8
8
  |---|---|--:|--:|--:|---|
9
9
  | `docs/LOGGING_STANDARD.md` | `LOG` → `logging.sh` | 10 | 13 | 30/30 | mechanical |
10
- | `docs/REST_API_DESIGN_GUIDELINES.md` | — | 0 | 0 | 0/69 | mechanical |
11
- | `docs/SCHEMA_CONVENTIONS.md` | — | 0 | 0 | 0/12 | mechanical |
10
+ | `docs/REST_API_DESIGN_GUIDELINES.md` | — | 0 | 0 | 0/75 | mechanical |
11
+ | `docs/SCHEMA_CONVENTIONS.md` | — | 0 | 0 | 0/13 | mechanical |
12
12
  | `docs/DOCUMENTATION_RULES.md` | — | 0 | 0 | 0/22 | latent |
13
13
  | `docs/LIFECYCLE_PATTERNS.md` | — | 0 | 0 | 0/22 | mechanical |
14
14
  | `docs/CHANGELOG_VOICE.md` | — | 0 | 0 | 0/3 | latent |
15
15
  | `docs/VERIFY_TIERS.md` | — | 0 | 0 | 0/4 | uncited |
16
16
  | `docs/greptile-learnings/RULES.md` | — | 0 | 0 | 0/115 | mechanical |
17
- | **corpus** | | | | **30/277** | |
17
+ | **corpus** | | | | **30/284** | |
18
18
 
19
19
  ## How to read this
20
20
 
@@ -93,16 +93,21 @@ Every root a user or operator can delete — draft, user, Library card, fleet, w
93
93
 
94
94
  ## Timestamps
95
95
 
96
- - **Type:** `BIGINT NOT NULL` — milliseconds since the Unix epoch, from the application clock (`afd_core::clock::UnixMillis`).
97
- - **Never** `TIMESTAMPTZ`, `TIMESTAMP` or `DEFAULT now()`.
96
+ - **Type:** `TIMESTAMPTZ(3) NOT NULL` (nullable for a NULL marker), so the database, the Rust `jiff::Timestamp` and the API agree on millisecond precision. Never `BIGINT` epoch integers or `TIMESTAMP` without a time zone.
97
+ - **Server lifecycle stamps** (`created_at`, `updated_at`) are written by the statement as `now()`: one database clock, one instant per transaction. No column `DEFAULT`; every write names its stamp in its `.sql` file.
98
+ - **Deadlines** are computed in SQL as `clock_timestamp() + $ttl::interval`, with the TTL bound as an exact interval.
99
+ - **A locking validity check** ("is this lease still live?") locks the target rows first, then reads `clock_timestamp()` once in a `MATERIALIZED` step that depends on the locked rows. A clock read in the `WHERE` or select list of a locking statement can run before the lock wait and accept an expired row.
100
+ - **A non-locking read or `SKIP LOCKED` sweep** compares against `statement_timestamp()`, which an index can use; `clock_timestamp()` cannot be an index bound.
101
+ - **`now()` and `CURRENT_TIMESTAMP` never decide an expiry**, because both are fixed at transaction start.
102
+ - A time supplied by another system is stored as given in its own column, apart from the received or processed stamp.
98
103
 
99
104
  ## Standard Columns
100
105
 
101
106
  | Column | Type | Required | Notes |
102
107
  |--------|------|----------|-------|
103
108
  | `id` | `UUID PRIMARY KEY` | Yes | UUIDv7, application-minted |
104
- | `created_at` | `BIGINT NOT NULL` | Yes | Set once at INSERT |
105
- | `updated_at` | `BIGINT NOT NULL` | If the row ever changes | Set at INSERT and every UPDATE |
109
+ | `created_at` | `TIMESTAMPTZ(3) NOT NULL` | Yes | `now()` at INSERT |
110
+ | `updated_at` | `TIMESTAMPTZ(3) NOT NULL` | If the row ever changes | `now()` at INSERT and every UPDATE |
106
111
 
107
112
  ## Audit Pattern
108
113
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentsfleet/orly",
3
- "version": "0.14.6",
3
+ "version": "0.14.7",
4
4
  "description": "Repository-scoped engineering harness: renders agent rules, materialises the gates that enforce them, and proves the PR boundary \u2014 for any coding agent.",
5
5
  "license": "MIT",
6
6
  "type": "module",