@jantstack/adonis-authz 2.4.0-alpha.6 → 2.4.0-alpha.8
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 +24 -4
- package/build/commands/authz_scopes_relay.d.ts +32 -0
- package/build/commands/authz_scopes_relay.d.ts.map +1 -1
- package/build/commands/authz_scopes_relay.js +79 -0
- package/build/commands/authz_scopes_relay.js.map +1 -1
- package/build/commands/authz_scopes_requeue.d.ts +17 -0
- package/build/commands/authz_scopes_requeue.d.ts.map +1 -1
- package/build/commands/authz_scopes_requeue.js +83 -5
- package/build/commands/authz_scopes_requeue.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/src/drivers/database_relations_driver.d.ts +17 -0
- package/build/src/drivers/database_relations_driver.d.ts.map +1 -1
- package/build/src/drivers/database_relations_driver.js +48 -5
- package/build/src/drivers/database_relations_driver.js.map +1 -1
- package/build/src/errors.d.ts +21 -0
- package/build/src/errors.d.ts.map +1 -1
- package/build/src/errors.js +7 -0
- package/build/src/errors.js.map +1 -1
- package/build/src/manager.d.ts +47 -2
- package/build/src/manager.d.ts.map +1 -1
- package/build/src/manager.js +500 -226
- package/build/src/manager.js.map +1 -1
- package/build/src/relations/manager.d.ts +19 -3
- package/build/src/relations/manager.d.ts.map +1 -1
- package/build/src/relations/manager.js +33 -8
- package/build/src/relations/manager.js.map +1 -1
- package/build/src/scope_outbox.d.ts +33 -0
- package/build/src/scope_outbox.d.ts.map +1 -1
- package/build/src/scope_outbox.js +48 -1
- package/build/src/scope_outbox.js.map +1 -1
- package/build/src/shared/transaction_guard.d.ts +5 -2
- package/build/src/shared/transaction_guard.d.ts.map +1 -1
- package/build/src/shared/transaction_guard.js +16 -2
- package/build/src/shared/transaction_guard.js.map +1 -1
- package/build/src/testing/scope_outbox_contract.d.ts +10 -0
- package/build/src/testing/scope_outbox_contract.d.ts.map +1 -1
- package/build/src/testing/scope_outbox_contract.js +71 -1
- package/build/src/testing/scope_outbox_contract.js.map +1 -1
- package/build/src/types.d.ts +66 -1
- package/build/src/types.d.ts.map +1 -1
- package/build/src/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,6 +31,8 @@ node ace authz:catalog:sync
|
|
|
31
31
|
|
|
32
32
|
`configure` registers the provider, the commands and the `appAccess` middleware, defines the env variables, and **publishes into your project** what belongs to you: the migration for the `authz_*` tables and two config files (`config/authorization.ts` for drivers and the scope tree, `config/app_acl.ts` for your role catalog). The suite compiles both published configs against the package on every run, so they cannot drift from it.
|
|
33
33
|
|
|
34
|
+
> **Upgrading to 2.4.0-alpha.7 on MySQL from 2.4.0-alpha.4 or earlier — stop the relay, do not roll.** alpha.5 and alpha.6 took both the old (64-char-truncated) and the new (stable, hashed) `GET_LOCK` names of the outbox lease so a mixed rolling deploy kept a single writer; alpha.7 takes only the new one (`SCOPE_OUTBOX_TAKES_LEGACY_LOCK = false`). A leftover alpha.4 relay holding the old name is **not seen** by alpha.7 — pinned by a case, on purpose — so upgrade with a full stop of every relay (cron and inline) and a restart. From alpha.5 or later, or on PostgreSQL/SQLite, nothing to do.
|
|
35
|
+
|
|
34
36
|
> **Upgrading to 2.4.0-alpha.4 — how the commands are registered.** `@jantstack/adonis-authz/commands` is now a real ace loader (it exports `getMetaData()`/`getCommand()`, built with `ListLoader` over the nine command classes, next to the named class exports). Until alpha.4 it only re-exported the classes, so the line `configure` writes — `commands: [() => import('@jantstack/adonis-authz/commands')]` — made **the whole `node ace` crash** at boot (`TypeError: loaderInstance.getMetaData is not a function`). If you registered the commands by wrapping the barrel in a `ListLoader(Object.values(m))` (the only way it worked before alpha.4), remove the wrapper and register the module directly: `() => import('@jantstack/adonis-authz/commands')`, which is what `node ace configure` writes. The wrapper now fails with `TypeError: command.serialize is not a function`, because the module also exports the loader functions: `Object.values(m)` includes them and `ListLoader.getMetaData()` calls `serialize()` on each value. The module has no `default` export on purpose — ace calls `getMetaData()` on the module namespace and never unwraps `default` (same shape as `@adonisjs/core/commands`).
|
|
35
37
|
|
|
36
38
|
For the OpenFGA driver, also install its SDK (optional peer): `npm i @openfga/sdk`. Everything that touches the SDK lives behind the **`@jantstack/adonis-authz/openfga` subpath** (`OpenFgaAuthorizationDriver`, `provisionOpenFgaStore`, `importAuthzFactsToOpenFga`, `openFgaAuthorizationModel`, `assertHolderTypes`); the main entry never imports it, so a database-only install boots without the SDK (the suite loads `index.ts` in a child process with `@openfga/sdk` blocked to prove it: *"index.ts (la entrada principal) carga con el SDK bloqueado"*). The published config imports the driver from that subpath *inside* the factory.
|
|
@@ -128,6 +130,7 @@ node ace authz:scopes:relay # drain the queue and apply the edges
|
|
|
128
130
|
node ace authz:scopes:relay --dry-run # list what is still unpropagated
|
|
129
131
|
node ace authz:scopes:relay --only=12,13 # apply only these entries (the receipt of scopes.*), 2.4.0-alpha.5
|
|
130
132
|
node ace authz:scopes:requeue --id=12 # return a parked entry to the queue (refuses to revert later applied changes), 2.4.0-alpha.5
|
|
133
|
+
node ace authz:scopes:relay --max-age=300000 # exit ≠ 0 if the oldest pending entry is older than 5 min (the age is always printed), 2.4.0-alpha.8
|
|
131
134
|
```
|
|
132
135
|
|
|
133
136
|
The package **does not impose a table**: the contract is the `ScopeOutbox` port (`enqueue`, `pending`, `markApplied`, `markFailed`, plus five optional ones: `dead` for parked entries, `acquire` for the single-writer lease and, since 2.4.0-alpha.5, `earlierPending` — the dependency probe of `only` —, `requeue` and `laterApplied` — the pair behind `authz:scopes:requeue`; in the same release `enqueue` may return `{ id }`, `pending` takes an optional `{ ids }` filter and `markFailed` an optional `{ transient }`, all three covariant: an implementation written against alpha.4 still compiles and works). Since 2.4.0-alpha.5 the port has a published judge, [`runScopeOutboxContract`](#the-outbox-contract-runscopeoutboxcontract-240-alpha5). `sqlScopeOutbox` is the published implementation over Lucid and `stubs/scopes_outbox_migration.stub` is its migration — **copy it into your migrations yourself**; `node ace configure` does not publish it, because the outbox is opt-in. Two things an implementation must do: write `enqueue` inside the transaction it is handed, and **`pending()` must return the whole unapplied queue, oldest first** — it must not hide an entry the relay did not filter through `{ ids }`, by tenant, by scope or by anything else: the order guarantee of the relay (a `moved` before the `attached` that depends on it) rests on the manager seeing the whole queue, and no guard of the manager can tell when a port hides rows on its own (that is exactly how the cross-tenant escalation measured in the audit is reproduced with a "filtering" outbox). It is a duty of the port and the contract judges it (O-08).
|
|
@@ -144,12 +147,24 @@ The relay is resumable and never silent: the report says *which* changes were ap
|
|
|
144
147
|
|
|
145
148
|
**When the parked set grows.** Nothing in the package shrinks it for you, on purpose: `authz:reconcile` never touches the outbox (the counter does not go down), `authz:scopes:requeue` has no `--all` (re-queueing an irreparable entry parks it again in one pass, and re-queueing a stale one is the reversal the probe refuses), and past `PARKED_CENSUS_MAX` (50,000) every pass is a 500 before applying anything. The remedy is an operator's SQL on **your** table: list `authz_scope_outbox` rows with `attempts >= maxAttempts` and `applied_at IS NULL`, read `last_error`, and either `authz:scopes:requeue --id` the ones whose cause is gone (a parent that exists again) or **delete** the irreparable ones (a parent that will never exist) — then `authz:reconcile --to=<driver>` rebuilds the tree from today's rows. Deleting a parked entry is safe for the tree only because the reconcile recomputes it; do not delete and skip the reconcile.
|
|
146
149
|
|
|
150
|
+
**The root `app` is not a scope key, and a parked entry only plugs the key of its own child (2.4.0-alpha.8).** Two corrections to the key rule above, both measured in a consumer. *First*, until alpha.7 the keys of an `attached`/`moved` were `[child, parent]` and the parent of every tenant registration is `app`, so every tenant registration shared a key with every other one: one parked `attached(orgA, app)` left every later registration `deferred · parked:` on every pass (five passes with `applied = []`), two concurrent registrations relayed inline left the second one `deferred · depends-on:` **with nothing parked at all** (the root was a global serialiser of sign-ups, and under (c2r) a fail-closed one: the new organisation granted nothing until the cron ran), and `authz:scopes:requeue` refused the parked one with a false conflict as soon as another tenant had registered. The root **never moves, is never purged and is never a child** (`scopes.attached(APP_SCOPE, …)`, `scopes.moved(APP_SCOPE, …)` and `scopes.detached(APP_SCOPE)` are 422), so two changes whose *only* shared scope is `app` commute and there is no order to keep: the manager now leaves the root out of the keys — decided on the reference (`type === 'app'` and a null uuid), never on the string `'app'`, and in the three places that build the list (the pass, the `only` probe, the requeue probe), also for blocks born from a real failure. The child's key still orders everything that matters: a `detached(orgA)` that failed still holds back an `attached(orgA, app)` with the same uuid, and a failed `moved(unit, app)` still holds back a later `moved(unit, orgB)`. The port is untouched: `sqlScopeOutbox.earlierPending`/`laterApplied` still know how to match the root as a parent; the manager just never asks. *Second*, the parked census now seeds only the key of the parked entry's **child**: a parked entry says the position of *its child* in the store is wrong, nothing about its new parent, so a `moved(otherUnit → orgA)` behind a parked `moved(unit → orgA)` is applied (until alpha.7 it was `deferred · parked:` for no reason and the store kept the old parent of everything under `orgA` with no expiry), while `attached(child, unit)` and `moved(unit → orgC)` — which name the parked child — stay `deferred · parked:<id>` and a `detached(unit)` still passes. When comparing two changes the manager still uses both keys; only the seeding of the census changed. Both are a change of observable behaviour: an `attached`/`moved` that used to come out `deferred` under those conditions is now applied, which is the bug.
|
|
151
|
+
|
|
147
152
|
**Under `only` the purge exemption holds too (2.4.0-alpha.6).** With `relayScopeChanges({ only })` the probe that finds an earlier unapplied entry sharing a scope key with yours now **inherits the origin of the block** of that entry, the same way the full pass tracks it: if the earlier entry is — directly or through its own dependencies — waiting only on a **parked** one (it would be `parked:<id>` or `depends-on:` of one in a full pass), a requested `detached` is **applied and purges**, exactly as in the full pass (invariant 11: a purge never waits for a parked entry). If the earlier entry is an `attached`/`moved` that is genuinely pending (it would be applied by the next full pass) or is held by a **real failure** of the pass, the `detached` stays `deferred` (`depends-on:<id>`), because applying a purge ahead of a pending `moved` of the same scope leaves drift. Until alpha.6 the probe marked every such deferral as a real block, so a deployment that only ever relayed inline never purged behind a parked entry (audit 🟡 of alpha.5, measured). The probe walks back one `earlierPending` query per hop over entries it does not apply, at most `RELAY_ORIGIN_MAX_HOPS` (100) hops; past that it assumes a real block and defers — towards safe, never applying without knowing. Entries you did not request are not touched: they keep their attempts and their place.
|
|
148
153
|
|
|
149
154
|
**Requeueing a parked entry: `node ace authz:scopes:requeue --id=<id>` (2.4.0-alpha.5).** A parked entry is never retried on its own. When you have fixed the cause — the missing parent is back, the store was reconciled — `authorization.requeueScopeChanges([id])` (platform API, like the relay; the command is its ace face) returns it to `pending()` with its old id, and **`attempts` is set to `maxAttempts − 1`**, neither `0` nor kept (owner's decision): if the cause is still broken the entry is parked again after **one** pass — and it contaminates its scope key only during that pass, so an automated requeue of something irreparable is not a permanent deferral tap for that tenant's new changes —, and if you fixed it, it lands on the first pass. No new column. It is all or nothing: an id that does not exist, is still pending, or was already applied is a 500 naming it and **no row is touched** (an applied entry never comes back: requeueing an applied `detached` would purge again). **The anti-reversion probe lives in the manager and has no exception.** Before touching the queue it asks the port (`laterApplied`) whether an entry *applied after* the parked one shares a scope key with it; if one does, requeueing would re-apply the old change on top of the new — measured: a parked `moved(unit → orgB)` requeued after `moved(unit → orgC)` was applied leaves the store with the **old** parent and a single edge, so nothing denounces it while the old tenant regains access — and the call is **500 `E_AUTHZ_REQUEUE_REFUSED`** naming the later entry and the key, with the right remedy (`authz:reconcile --to=<driver>`, which recomputes with today's tree). **`--force` exists only in the ace command**: it prints the diff — the parked entry and every applied entry it would revert — and then requeues anyway; `requeueScopeChanges(ids, { force: true })` is what the command calls and nothing reachable over HTTP should. The probe is the manager's, not the table's, so a third-party `ScopeOutbox` gets it for free by implementing the two optional methods (`requeue` and `laterApplied`); without them the command says so (500 `E_AUTHZ_UNSUPPORTED`). Two limits of the probe, written: the diff lists at most 100 later entries per parked one and says when it is cut (`truncated`, "… y más"); and **`laterApplied` only sees the outbox** — `authz:reconcile` does not write to it, so a requeue *after* a reconcile can revert what the reconcile just certified and the probe has nothing to compare against: **do not requeue after reconciling**; if you already did, the remedy is to reconcile again.
|
|
150
155
|
|
|
156
|
+
**Closing the incident in one call: `requeueScopeChanges(ids, { apply: true })` / `node ace authz:scopes:requeue --id=<id> --apply` (2.4.0-alpha.8).** Measured in a consumer (panel relay 2, tester P14): after `requeue` the entry comes back with its *original* id — smaller than everything enqueued since — so the tenant's next inline `relayScopeChanges({ only })` found it *in front* and came out `deferred · depends-on:<id>`; the incident needed a full pass behind the command. With `apply: true` the entry is requeued **and relayed in the same call**, and the relay's report travels in `report.relay`. Four rules, each pinned by a case: **one lease** for the whole call, taken *first* — if another pass holds it the result is `relay.busy === true` with **zero rows requeued** and the command exits ≠ 0 (requeueing and then saying "busy" would leave the entry pending with `attempts = maxAttempts − 1`, to be parked again by the next pass and poison its key once more; `waitForLeaseMs` composes with it); it **passes the freeze barrier** before touching anything (with `apply` the command writes the tree; without `apply`, `requeue` still does not, it only touches the queue); it **does not compose with `force`** (500 `E_AUTHZ_CONFIG` — with both, reverting the tree over what was applied later would be a single call); and the internal `only` is **exactly the requeued ids**: what depended on the parked entry is *not* dragged along (it stays `deferred`/pending, the report says so — `remaining`, and the command prints it), because `only` is *your receipt* and nothing else. The anti-reversion probe still runs before anything is requeued (a refusal is 500 with zero driver calls, and the lease is released). If the cause is still broken, the same call parks the entry again (`attempts` goes from `maxAttempts − 1` to `maxAttempts`, `relay.failed` says why); a 503 from the store leaves it pending with `attempts = maxAttempts − 1`, as always. `--apply` is explicit, never the default: without it the command behaves exactly as before. Platform API, like the relay: never reachable over HTTP.
|
|
157
|
+
|
|
151
158
|
**The relay is a single writer.** `pending()` reserves nothing, so two passes at once (a Kubernetes `CronJob` with the default `concurrencyPolicy: Allow`, two replicas, a pass that lasts longer than its interval) work on the same batch: the straggler re-applies an old `attached` after the other applied the new `moved`, and the store is left with the **old parent and a single edge, so nothing denounces it** — the old tenant keeps access to a subtree that is no longer theirs (measured). `sqlScopeOutbox.acquire()` takes a lease for the pass — a server-side lock on PostgreSQL and MySQL, a process-wide one on SQLite — and a second simultaneous pass does nothing and says so (`busy`). Since 2.4.0-alpha.5 that is **observed between real processes** (`tests/scope_outbox_multiprocess.spec.ts`: a second process relays the same queue while the first holds the lease ⇒ `busy`, zero writes, on PostgreSQL and MySQL), and the SQLite limitation is pinned the same way — the second process *does* apply over the first: **SQLite is not the engine for two replicas of the relay**. If you implement the port yourself and skip `acquire`, run the relay one pass at a time.
|
|
152
159
|
|
|
160
|
+
**Waiting for the lease instead of giving up: `relayScopeChanges({ waitForLeaseMs })` / `--wait-lease-ms` (2.4.0-alpha.8).** By default (`waitForLeaseMs: 0`) a pass that finds the lease taken reports `busy` at once — one `acquire()` attempt, the behaviour above, unchanged. With a positive value the pass retries `acquire()` every `RELAY_LEASE_POLL_MS` (25 ms; measured 0.29 ms per busy poll in PostgreSQL, and a failed poll holds no connection between attempts) until the lease is free or the budget is spent, and then reports `busy` — bounded, never a hang. It is opt-in and it **needs a pool of at least 2** connections, like `{ transaction }`: with a pool of 1 the waiting pass may be holding the very connection the pass that has the lease needs. Do not size it to a full backlog: a pass over 2,000 entries with real backend writes takes tens of seconds; the option exists so that an operator's `authz:scopes:requeue --apply --wait-lease-ms` does not lose systematically against the cron, not to queue relays behind each other.
|
|
161
|
+
|
|
162
|
+
**How old is the queue? `report.queue` and `--max-age` (2.4.0-alpha.8).** Measured in a consumer (panel relay 2, auditor B2): with the store down, ten relay passes in a row reported `attempts = 0` on every entry (a 503 does not consume an attempt, since alpha.5), `dead = 0` and **no notion of time anywhere** — the cron was red because of `failures`, but nobody could tell since when, and nothing distinguished a queue that has been stuck for an hour from one that was enqueued a second ago. Every relay report now carries **`queue: { oldestPendingAt, pendingAgeMs }`**: the `createdAt` of the **oldest pending entry of the whole queue** — the `pending(1)` the pass already ran at the end for `remaining`, so it costs no extra query — and its age, `max(0, now − createdAt)`, measured with the manager's injectable clock (`config.clock`; no `Date.now()` in the package sources, the suite greps for it). It is **always global, also under `only`**, like `remaining` and `dead`: it names nobody, it only says "there is backlog". Both fields are `null` when nothing is *pending* (an empty queue, or only parked entries — those have their own alarm, `deadCount`, below) and when the port does not provide `createdAt`: a third-party `ScopeOutbox` whose entries carry no `createdAt` reads as `null`, **never as `0` or "now"** (the same rule as `stillGranting`: "I do not know" does not degrade to a number). `sqlScopeOutbox` returns it from the `created_at` column it already had (`createdAt?: Date` is an optional field of `PendingScopeTreeChange`; the outbox contract has a `createdAt` capability pair). Read the number for what it is: **the age is the clock of whoever enqueued** — another replica with a skewed clock stamps its own `created_at`, so the age is clamped at zero and never negative, and it is not a decision the package takes (audit stamps are not decisions). The command prints it on every pass; **`--max-age=<ms>`** makes the pass exit ≠ 0 when the age is above the threshold — **only with a threshold**: without it the age is reported and never fails the run, because a cron that goes red for ever over a queue that simply has backlog is a cron nobody reads. Parking by age (`parkAfterMs`) is *not* offered, on purpose — see the changelog: it reintroduces, with another clock, the mass parking alpha.5 removed, and it was measured to make every pass a 500 once the census fills.
|
|
163
|
+
|
|
164
|
+
**Enqueueing outside your transaction: `sqlScopeOutbox({ requireTransaction: true })` (2.4.0-alpha.8).** The mitigation above is *entirely* the `{ transaction: trx }` you hand to `scopes.attached/moved/detached`. Without it the call still enqueues — the row commits on its own, outside your unit of work — and if your transaction rolls back afterwards **the change stays in the queue and the relay applies it to the store**: your database says one parent, the PDP another, and nothing on your side can show it (the S5 leak, on the *correct-use* path; measured by the tester of panel relay 2, P13). With `requireTransaction: true` that call is **500 `E_AUTHZ_CONFIG` before the `INSERT`**, naming exactly that leak, through the manager and through the outbox alone; with an open transaction of the outbox's connection it enqueues as always. The default is **`false` in 2.4** — the published outbox contract (`runScopeOutboxContract`) enqueues without a transaction in its `ctx()`, and a `true` default would turn every third-party outbox red — and **the default becomes `true` in 2.5** (owner's decision), together with the contract passing the transaction in `ctx()` (a prerequisite of that release; without it the default cannot change). Turn it on today: it is the letter of the mitigation, and the only thing you lose is the ability to enqueue by mistake. A transaction that is **already committed or rolled back** is a different 500, below.
|
|
165
|
+
|
|
166
|
+
**What the report says about the parked set — `deadCount`, `deadDetail`, and the `origin` of a deferral (2.4.0-alpha.8).** Three small corrections to the report, each measured in a consumer. **`deadCount`** is always present and always global: `dead[]` is capped at `batchSize` (100), so `dead.length` *lies* once more than that is parked; the alarm "the queue does not converge" now rests on the count, which comes from the census the pass already reads for the key plug (no extra query). **Under `only`, `dead[]` carries `id`, `change` and `attempts` and — by default — no `error`**: the inline recipe runs inside a tenant's request, and the error text is what the backend said about *another* tenant's tree; pass `deadDetail: 'full'` to get it back (without `only` nothing changes, and the entries you *asked for* keep their `reason` in `entries`, they are yours). Read this as the safe-fields rule for a report you might be tempted to surface: `deadCount` and the ids say "there is a parked backlog" and nothing else; `change` still names scopes of other tenants (the actor never travels in `dead[]`), so the rule above stands — do not return the report over HTTP. And every `deferred` entry now carries **`origin`**: `'parked'` (held, directly or transitively, by a parked entry — the only origin that lets a `detached` through, invariant 11), `'failed'` (held by a real failure of this pass), or `'pending'` (under `only`: held by an earlier entry that is genuinely pending and was not requested — it will land on the next full pass). Until alpha.8 `depends-on:<id>` did not distinguish the last two and the origin lived only in the Spanish prose of `deferred[].error`; it is derived from the block itself, never parsed back from the text of `reason`.
|
|
167
|
+
|
|
153
168
|
**What the outbox does not fix, in plain words.** Between your commit and the relay pass there is a lag of **seconds during which FGA decides with the old tree**. What that costs you depends on the change, and the two directions have **opposite signs** (2.0, the `rooted` relation of the `facts` model — see [What a scope that is not attached grants](#what-a-scope-that-is-not-attached-grants)):
|
|
154
169
|
|
|
155
170
|
- after a `moved` or a `detached` it is a **temporary fail-open**: the **old tenant keeps access** to the moved subtree, and the scope you deleted is only purged when the relay runs;
|
|
@@ -185,6 +200,8 @@ The capability behind the first two rows is **`transactionalWrites`** (same name
|
|
|
185
200
|
|
|
186
201
|
#### The rule: the write goes through your transaction, the authority never does
|
|
187
202
|
|
|
203
|
+
**A transaction that is already committed or rolled back is refused before any statement (2.4.0-alpha.8).** Lucid keeps `isTransaction === true` on a finished transaction, so until alpha.7 `assertCallerTransaction` let it through and the statement blew up inside knex as **503 `E_AUTHZ_BACKEND_UNAVAILABLE`** ("the backend did not answer") — which is false: it is the caller's wiring (a `trx` reused after `await trx.commit()`). It is now the fourth check of the shared guard (`isCompleted === true` ⇒ **500 `E_AUTHZ_CONFIG`** naming the operation, "confirmada o revertida", zero statements), in the three places that take `{ transaction }`: `sqlScopeOutbox.enqueue`, the `database` roles driver and the `database` relations driver — pinned in the four engines.
|
|
204
|
+
|
|
188
205
|
**Through your transaction: the write, and only the write** — the `INSERT`/`UPDATE`/`DELETE`, plus the "does it already exist?" read that belongs to it (a `grant` and its re-grant, a `relate` and its renewal, inside the same transaction see each other). **Through the engine's own connection, always: the authority** — the freeze barrier (row `id = 2` of `authz_catalog_version`), the catalog memo, `resolveChain`, F-05 and `assertWrite`. They are decided **before the first statement on your transaction** (the suite spies on the transaction and counts zero), and a transaction opened *before* a freeze does not get to decide it is not frozen (its snapshot would say so under InnoDB's REPEATABLE READ, PostgreSQL at that level and SQLite in WAL — the suite reproduces the bypass on the three engines and pins the 503). Until 2.4.0-alpha.2 the barrier *was* read through the caller's transaction "so a pool of 1 would not deadlock"; that traded authority for availability and it is gone.
|
|
189
206
|
|
|
190
207
|
Hence the deployment rule, said out loud: **`{ transaction }` requires a pool of at least 2.** With a pool of 1 (SQLite `:memory:`, the suite's default mode) the authority cannot get a connection while you hold the only one, and the write is **503 `E_AUTHZ_BACKEND_TIMEOUT` at `freezeTimeoutMs`** (config, default 5000 ms — the barrier's own total deadline, pool wait included; knex's own `acquireConnectionTimeout` would have made it 60 s) with **zero statements on your transaction**: fail-closed, never a hang, never a bypass. A pool-of-1 deployment declares it — `new DatabaseAuthorizationDriver({ transactionalWrites: false })`, `new DatabaseRelationsDriver(config, { transactionalWrites: false })` — and gets gate 1's immediate 500 instead; the driver declares what its deployment can do, the package does not guess the pool. This also applies to `scopes.*` with `{ transaction }` (the outbox): a single-connection deployment that used it has to raise the pool (breaking of deployment, lot L-1). While the engine is **frozen**, `{ transaction }` is the retryable 503 `E_AUTHZ_FROZEN` before the first statement on your transaction.
|
|
@@ -743,7 +760,7 @@ Every error the package raises carries `status` and `code`. A standard AdonisJS
|
|
|
743
760
|
| `E_AUTHZ_WITHIN_REQUIRED` | 422 | `requireWithin` set and a write without `within` (2.1) |
|
|
744
761
|
| `E_AUTHZ_WITHIN_ROOT_FORBIDDEN` | 422 | `requireWithin: 'non-root'` and `within: APP_SCOPE` (2.1) |
|
|
745
762
|
| `E_AUTHZ_ACTOR_REQUIRED` | 422 | `requireActor: true` and a write without `actor` (2.1); on the relations port (`relations.requireActor ?? requireActor`) the four writes, `purgeObject`/`purgeSubject` included since 2.4.0-alpha.3 |
|
|
746
|
-
| `E_AUTHZ_SCOPE_DEPTH_LIMIT` | 422 | `scopes.attached`/`scopes.moved` with `scopes.maxDepth` declared: the child would end up more than `maxDepth` `parent` hops from `app` (the parent's chain + 1). Refused **before** writing or enqueueing; the gate is opt-in and partial — see [Limits](#limits) (2.4.0-alpha.5) |
|
|
763
|
+
| `E_AUTHZ_SCOPE_DEPTH_LIMIT` | 422 | `scopes.attached`/`scopes.moved` with `scopes.maxDepth` declared: the child would end up more than `maxDepth` `parent` hops from `app` (the parent's chain + 1). Refused **before** writing or enqueueing; the gate is opt-in and partial — see [Limits](#limits) (2.4.0-alpha.5). Since 2.4.0-alpha.8 the `message` is written for the tenant that made the request — the depth reached, `scopes.maxDepth`, "nothing written or enqueued" — and names no backend: *why* the ceiling exists (`FACTS_MAX_RESOLVE_DEPTH`, `OPENFGA_RESOLVE_NODE_LIMIT`) travels in `error.details` for the operator's log |
|
|
747
764
|
| `E_AUTHZ_TOO_MANY_SCOPES` | 422 | `authorizedScopes`/`expandExcludedSubtrees` over `maxScopes`, or `descendantsOf` over `maxNodes` (`sqlDescendantsOf`: also a possible cycle) — never a partial list (2.1) |
|
|
748
765
|
| `E_AUTHZ_BACKEND_UNAVAILABLE` | 503 | facts backend or SQL catalog did not answer (both drivers, catalog sync/diff and the `authz_catalog_version` check included); the version row is missing or unreadable ("migración 2.0 no aplicada": fail-closed, never version 0); a per-check `error` in an OpenFGA `batchCheck` |
|
|
749
766
|
| `E_AUTHZ_BACKEND_TIMEOUT` | 503 | `timeoutMs` elapsed (subclass of the above) |
|
|
@@ -751,7 +768,7 @@ Every error the package raises carries `status` and `code`. A standard AdonisJS
|
|
|
751
768
|
| `E_AUTHZ_FREEZE_HELD` | 423 | `freeze()` (or a second `authz:reconcile`) found a live freeze owned by someone else: two windows never interleave, and only the owner's token — or `authz:unfreeze` — lifts one. The message names the holder, the reason and the fence |
|
|
752
769
|
| `E_AUTHZ_RESOLVER_FAILED` | 503 | your `resolveChain`, `parentOf` or `descendantsOf` threw or answered a malformed scope; `descendantsOf` and `resolveChain` disagree in `authorizedScopes`; a subtree to exclude cannot be enumerated |
|
|
753
770
|
| `E_AUTHZ_WRITE_CONFLICT` | 409 | an `openfga` write kept clashing with another transaction over the same tuples (FGA answers `Aborted`/409, or 400 "cannot write a tuple which already exists"): the driver re-reads and re-applies, and only gives up after three rounds. The backend answered, so this is never a 503 — retry the write. Also (2.4.0-alpha.2, lot L-3) a `database` `grant`/`deny` **inside your transaction** (`{ transaction }`) that hits the UNIQUE because another transaction committed the same fact while yours was open: outside a transaction the driver re-reads and keeps the winner's row; inside yours it cannot (PostgreSQL has already aborted your transaction — every later statement is `25P02` until you roll back — and under REPEATABLE READ the re-read would not see the winner), so it **poisons your transaction**: roll back and retry. Measured on all three engines, see [writing inside your transaction](#writing-inside-your-transaction-240-alpha2). The same for a `database` `relate`/`unrelate`/`purge*` inside your transaction (lot L-4) — on a UNIQUE clash of a tuple (userset, and since lot L-4b holder too: `subject_relation` is `''`, never `NULL`), and on a **deadlock** (two transactions writing two tuples in crossed order: PostgreSQL `40P01`, MySQL `1213`; the engine's victim gets this 409, never a hang nor the raw error) |
|
|
754
|
-
| `E_AUTHZ_CONFIG` | 500 | `relayScopeChanges({ only: [] })`, a `ScopeOutbox` that ignores `pending(limit, after, { ids })` and returns an entry that was not asked for, `requeueScopeChanges` with an id that is not parked (or an empty list), or a `scopes.maxDepth` that is not an integer ≥ 1 (2.4.0-alpha.5); contradictory config (`holderTypes` not injective or a holder type not declared in it, `scopes.*` without resolver, `appAccess` without `permission`, `catalog` together with `catalogRevalidate`, an invalid `maxAgeMs`); `bumpAuthzCatalogVersion` called without the writing transaction's client; a `{ transaction }` handed to `sqlScopeOutbox` that is not an open Lucid transaction of the outbox's own connection (another connection, a `QueryClient`, or the whole `db` — the message names both connections); `requireTransactionalWrites: true` (root, or `relations.requireTransactionalWrites`) with an active driver that does not declare `transactionalWrites: true` — raised **when the driver is resolved**, so reads fail too and the deployment does not start (2.4); a `relations.assertWrite` that returns a promise — or any value at all: `false`, `true`, a thenable function (it must be synchronous and it does not return verdicts: it throws or it does not; the write is refused before the driver, 2.4.0-alpha.3) |
|
|
771
|
+
| `E_AUTHZ_CONFIG` | 500 | `relayScopeChanges({ only: [] })`, a `ScopeOutbox` that ignores `pending(limit, after, { ids })` and returns an entry that was not asked for, `requeueScopeChanges` with an id that is not parked (or an empty list), or a `scopes.maxDepth` that is not an integer ≥ 1 (2.4.0-alpha.5); contradictory config (`holderTypes` not injective or a holder type not declared in it, `scopes.*` without resolver, `appAccess` without `permission`, `catalog` together with `catalogRevalidate`, an invalid `maxAgeMs`); `bumpAuthzCatalogVersion` called without the writing transaction's client; a `{ transaction }` handed to `sqlScopeOutbox` that is not an open Lucid transaction of the outbox's own connection (another connection, a `QueryClient`, or the whole `db` — the message names both connections), **or one that is already committed or rolled back** (`isCompleted`, 2.4.0-alpha.8 — the same check in the roles and relations drivers; it used to surface as a 503); `sqlScopeOutbox({ requireTransaction: true })` enqueueing without `{ transaction }` (2.4.0-alpha.8; the default becomes `true` in 2.5); `relayScopeChanges({ deadDetail })` with a value other than `'full'`; `requireTransactionalWrites: true` (root, or `relations.requireTransactionalWrites`) with an active driver that does not declare `transactionalWrites: true` — raised **when the driver is resolved**, so reads fail too and the deployment does not start (2.4); a `relations.assertWrite` that returns a promise — or any value at all: `false`, `true`, a thenable function (it must be synchronous and it does not return verdicts: it throws or it does not; the write is refused before the driver, 2.4.0-alpha.3) |
|
|
755
772
|
| `E_AUTHZ_ROLE_IS_NOT_ACCESS` | 500 | `appAccess({ role })` |
|
|
756
773
|
| `E_AUTHZ_INTERNAL` | 500 | package invariant violated (empty scope set on a write, misaligned batch, a third-party `authorizeMany` answering the wrong shape, a `Read` continuation token that never advances or more than 10,000 pages, a corrupt `assignable_at`/`owner_scope_key` row) |
|
|
757
774
|
| `E_AUTHZ_PURGE_INCOMPLETE` | 500 | `purgeScope` could not prove zero; on the relations port, `purgeObject`/`purgeSubject` of `openfga` could not prove zero after deleting — the error carries the `markPartialWrite` mark and the event was emitted with `indeterminate: true` (2.4.0-alpha.3) |
|
|
@@ -969,6 +986,7 @@ runScopeOutboxContract({
|
|
|
969
986
|
acquire: true, // acquire() is a held, exclusive lease; false ⇒ the relay never says busy
|
|
970
987
|
earlierPending: true, // the dependency probe of `only`; false ⇒ the manager pages pending(), same verdict
|
|
971
988
|
requeue: true, // requeue + laterApplied (needs dead); false ⇒ requeueScopeChanges is 500 E_AUTHZ_UNSUPPORTED
|
|
989
|
+
createdAt: true, // entries carry createdAt (the enqueue stamp): the relay reports the queue age; false ⇒ queue is { null, null } (2.4.0-alpha.8)
|
|
972
990
|
},
|
|
973
991
|
limits: { maxAttempts: 3 }, // with dead: true — what your outbox parks at (≥ 3)
|
|
974
992
|
makeOutbox: () => myOutbox({ maxAttempts: 3 }), // fresh, over an empty queue
|
|
@@ -977,7 +995,7 @@ runScopeOutboxContract({
|
|
|
977
995
|
})
|
|
978
996
|
```
|
|
979
997
|
|
|
980
|
-
|
|
998
|
+
Seventeen cases per harness (2.4.0-alpha.8): nine core duties — including O-07b, the root `app` is not a scope key — and one face per each of the eight capabilities. Declaring `dead: true` without `limits.maxAttempts`, `requeue: true` without `dead: true`, or `transactionalEnqueue: true` without `transactions` throws at registration — a promise without a judge does not pass. The package runs it on `sqlScopeOutbox` over the four engines and on an in-memory double (`makeScopeOutbox`, exported too) with all capabilities and with none, so both faces of every pair run on every `npm test`.
|
|
981
999
|
|
|
982
1000
|
## OpenFGA tooling
|
|
983
1001
|
|
|
@@ -1119,16 +1137,18 @@ Every bound the package enforces or measured, in one place, each with the consta
|
|
|
1119
1137
|
| `Read` page size (enumerations, `enumerateFacts`, `enumerateRelations`) | — | 100 | `openfga` | the server's hard cap: `pageSize: 1000` is a 400; the driver pages until the continuation token is empty — a page with zero tuples **and** a token is not the end; a token that repeats, more than 10,000 pages, or a response without `tuples` is 503 `E_AUTHZ_BACKEND_UNAVAILABLE`, never a shorter enumeration |
|
|
1120
1138
|
| `enumerateRelations(partition)` / `enumerateRelationsMany(partitions)` cost | — | O(tuples in the store) **per sweep** — one sweep per `enumerateRelations` call, **one sweep for K partitions** with `enumerateRelationsMany` / `reconcileRelations({ partitions })` (2.4.0-alpha.6) | `openfga`, the *source* of `authz:relations:reconcile` | there is no prefix `Read` in OpenFGA (a prefix object answers 200 with **zero** tuples, measured: never "optimise" it that way — the suite spies the filter of every `read`, it is always `{}`); measured 15,000 tuples ⇒ 151 pages ≈ 0.5 s per sweep whatever the partition size; 3 partitions in a 5,000-tuple store: **150 reads / 502–558 ms** as three sweeps → **50 reads / 170–209 ms** as one ([the table](#the-source-sweep-measured)). Run it from a job or a cron, **never from a tenant request** |
|
|
1121
1139
|
| Hops the `only` probe walks back to inherit a block's origin | `RELAY_ORIGIN_MAX_HOPS` | 100 | the manager, `relayScopeChanges({ only })` | the origin is taken as a real block and the entry stays `deferred` (the purge waits for a full pass); one `earlierPending` query per hop |
|
|
1140
|
+
| Interval between `acquire()` retries under `waitForLeaseMs` | `RELAY_LEASE_POLL_MS` | 25 ms | the manager, `relayScopeChanges({ waitForLeaseMs })` and `requeueScopeChanges({ apply, waitForLeaseMs })` | the budget is spent in polls of this size; past it the pass reports `busy` (default `waitForLeaseMs` is 0: one attempt) |
|
|
1122
1141
|
| Scopes a `pending()` pass reads per round trip | `DEFAULT_RELAY_BATCH` | 100 | `authz:scopes:relay` (`--batch-size`) | the pass keeps paging |
|
|
1123
1142
|
| Changes one relay pass applies | `DEFAULT_RELAY_LIMIT` | 10,000 | `authz:scopes:relay` (`--limit`) | the rest stays pending (`remaining: true`); run again |
|
|
1124
1143
|
| Failed attempts before an outbox entry is parked | `maxAttempts` | 5 | `sqlScopeOutbox` | the entry leaves `pending()`, shows in `dead()` on every pass and the command exits ≠ 0; a 503 of the backend does not count; `authz:scopes:requeue --id` brings it back |
|
|
1144
|
+
| Longest wait for the relay lease (`waitForLeaseMs`) | `RELAY_WAIT_LEASE_MAX_MS` | 60,000 | `relayScopeChanges` / `requeueScopeChanges({ apply })` | 500 `E_AUTHZ_CONFIG` above it — a wait without a cap hangs the tenant's request; shorten the pass (`only`, `limit`) instead. The wait is counted in polls of `RELAY_LEASE_POLL_MS`, so it ends even under a frozen injected clock |
|
|
1125
1145
|
| Parked outbox entries the relay can census per pass | `PARKED_CENSUS_MAX` | 50,000 | the manager, every relay pass (read in full, whatever `--limit`) | 500 `E_AUTHZ_CONFIG` before anything is applied — the relay of **every** tenant stops, loudly, never a blind plug. Measured cost of the census under the lease: 1,000 parked ⇒ 16 ms, 10,000 ⇒ 69 ms, 49,000 ⇒ 199 ms per pass. The way out is below (*When the parked set grows*) |
|
|
1126
1146
|
| Answer size of `authorizedScopes` / `expandExcludedSubtrees` | `maxScopes` | 1,000 | the manager | 422 `E_AUTHZ_TOO_MANY_SCOPES`, never a partial list (per call it can only be lowered) |
|
|
1127
1147
|
| Nodes one `descendantsOf` call may return | `maxDescendants` | 10,000 | the manager (`maxNodes` handed to your resolver) | 422 `E_AUTHZ_TOO_MANY_SCOPES`; both bounds are capped at `MAX_SCOPE_BOUND` (10,000,000) |
|
|
1128
1148
|
| Local roles a `prune-orphans` pass may read | `maxLocalRoles` | 10,000 | `pruneOrphanRoles` | 500 `E_AUTHZ_TOO_MANY_LOCAL_ROLES`, never a partial list |
|
|
1129
1149
|
| Ancestors `hierarchicalScopeResolver` walks | `maxDepth` (resolver option) | 64 in the README example | your `resolveChain` | 500 `E_AUTHZ_SCOPE_TOO_DEEP`, never a truncated chain |
|
|
1130
1150
|
|
|
1131
|
-
**`scopes.maxDepth` — the depth gate (2.4.0-alpha.5).** Declare it in `config.scopes` and `scopes.attached`/`scopes.moved` refuse, with **422 `E_AUTHZ_SCOPE_DEPTH_LIMIT`**, any child that would end up more than `maxDepth` `parent` hops from `app` — the parent's chain plus one — **before writing or enqueueing** (zero driver calls, zero outbox rows) and at zero extra I/O: the parent's chain is already resolved for the cycle check. Without the option there is no gate: a tree of any depth stays legal, and the `database` driver pays nothing. It lives in the config and not in `capabilities()` because a capability is a boolean by doctrine (`capabilities()` normalises with `=== true`); and it is the same knob `authz:reconcile` uses to report chains above the ceiling as drift (`drift.tooDeep`, exit ≠ 0 — a scope legal in `database` and a 503 in `openfga` belongs in CI, not in the first tenant that reaches it; without the option the verifier uses `FACTS_MAX_RESOLVE_DEPTH`). Set it to `22` for the `facts` model: then every question stays within the measured ceiling whichever ancestor carries the binding. **The gate is **partial**, and sold as such**: it looks at the *destination* of the child, not at the height of the subtree a `moved` drags along — a subtree of height *h* moved under a parent at depth *d* leaves its leaves at *d + h + 1*, and the gate does not see them; measuring that would need `descendantsOf` on the write path, which is forbidden. The reconcile report is what catches those afterwards. Why a gate at all: past the ceiling the model answers 503 for the whole subtree (a DoS within reach of whoever can nest sub-scopes), and the cost of each relayed `attached` grows with the depth of its neighbour (measured ×5.8 from depth 1 to 34) on the queue every tenant shares.
|
|
1151
|
+
**`scopes.maxDepth` — the depth gate (2.4.0-alpha.5).** Declare it in `config.scopes` and `scopes.attached`/`scopes.moved` refuse, with **422 `E_AUTHZ_SCOPE_DEPTH_LIMIT`**, any child that would end up more than `maxDepth` `parent` hops from `app` — the parent's chain plus one — **before writing or enqueueing** (zero driver calls, zero outbox rows) and at zero extra I/O: the parent's chain is already resolved for the cycle check. Without the option there is no gate: a tree of any depth stays legal, and the `database` driver pays nothing. It lives in the config and not in `capabilities()` because a capability is a boolean by doctrine (`capabilities()` normalises with `=== true`); and it is the same knob `authz:reconcile` uses to report chains above the ceiling as drift (`drift.tooDeep`, exit ≠ 0 — a scope legal in `database` and a 503 in `openfga` belongs in CI, not in the first tenant that reaches it; without the option the verifier uses `FACTS_MAX_RESOLVE_DEPTH`). Set it to `22` for the `facts` model: then every question stays within the measured ceiling whichever ancestor carries the binding. **How to size it: `maxDepth` counts `parent` hops from the child to `app`, so a tree of N levels below `app` needs `scopes.maxDepth = N`** — not N + 1: the parent's chain includes `app`, but a child at exactly `maxDepth` hops *enters* (`chain.length > maxDepth` is the refusal). Pinned by an executable case (`scope_depth.spec.ts`): a tree of exactly three levels (organization → unit → sub-unit), `maxDepth: 2` refuses the third level with 422, `maxDepth: 3` lets it in. The 422's `message` is for the tenant (depth, `scopes.maxDepth`, nothing written or enqueued); the reason the ceiling exists goes to `error.details` (2.4.0-alpha.8). **The gate is **partial**, and sold as such**: it looks at the *destination* of the child, not at the height of the subtree a `moved` drags along — a subtree of height *h* moved under a parent at depth *d* leaves its leaves at *d + h + 1*, and the gate does not see them; measuring that would need `descendantsOf` on the write path, which is forbidden. The reconcile report is what catches those afterwards. Why a gate at all: past the ceiling the model answers 503 for the whole subtree (a DoS within reach of whoever can nest sub-scopes), and the cost of each relayed `attached` grows with the depth of its neighbour (measured ×5.8 from depth 1 to 34) on the queue every tenant shares.
|
|
1132
1152
|
|
|
1133
1153
|
### Operational notes for the SQL engines
|
|
1134
1154
|
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import { BaseCommand } from '@adonisjs/core/ace';
|
|
2
2
|
import { CommandOptions } from '@adonisjs/core/types/ace';
|
|
3
|
+
import type { ScopeRelayQueue } from '../src/types.js';
|
|
4
|
+
/**
|
|
5
|
+
* **La edad de la cola en el comando** (2.4.0-alpha.8; panel relay 2,
|
|
6
|
+
* cuestión 5), en una función PURA (patrón `requeueLines`): qué se imprime y
|
|
7
|
+
* si `--max-age` la convierte en salida ≠ 0.
|
|
8
|
+
*
|
|
9
|
+
* - Sin cabeza con fecha (`pendingAgeMs === null`: cola vacía, solo
|
|
10
|
+
* aparcadas, o una outbox de terceros sin `createdAt`) no se dice nada y
|
|
11
|
+
* nunca se alarma.
|
|
12
|
+
* - Con edad, se INFORMA siempre (es lo que faltaba en el B2 de COGNITIV:
|
|
13
|
+
* diez pasadas con el store caído y nadie sabía desde cuándo).
|
|
14
|
+
* - **Solo con umbral** (`--max-age=<ms>`) y edad POR ENCIMA es `exceeded`
|
|
15
|
+
* (exit ≠ 0): sin umbral un cron no puede quedarse en rojo para siempre
|
|
16
|
+
* por una cola que simplemente tiene backlog. Un umbral inválido lanza.
|
|
17
|
+
*/
|
|
18
|
+
export declare function queueAgeLines(queue: ScopeRelayQueue, maxAgeMs: number | undefined, remaining?: boolean): {
|
|
19
|
+
lines: Array<{
|
|
20
|
+
level: 'info' | 'warning' | 'error';
|
|
21
|
+
message: string;
|
|
22
|
+
}>;
|
|
23
|
+
exceeded: boolean;
|
|
24
|
+
};
|
|
3
25
|
/**
|
|
4
26
|
* Drena la OUTBOX del árbol de scopes y aplica los cambios al driver
|
|
5
27
|
* (3b-2d; panel 2, cruce 4 · S5).
|
|
@@ -12,6 +34,14 @@ import { CommandOptions } from '@adonisjs/core/types/ace';
|
|
|
12
34
|
* node ace authz:scopes:relay --dry-run # solo dice qué haría
|
|
13
35
|
* node ace authz:scopes:relay --limit 500 # una pasada acotada
|
|
14
36
|
* node ace authz:scopes:relay --only=12,13 # SOLO esas entradas (2.4.0-alpha.5)
|
|
37
|
+
* node ace authz:scopes:relay --wait-lease-ms=5000 # espera el lease hasta 5 s en vez de salir `busy` (2.4.0-alpha.8)
|
|
38
|
+
* node ace authz:scopes:relay --max-age=300000 # exit ≠ 0 si la pendiente más antigua lleva > 5 min (2.4.0-alpha.8)
|
|
39
|
+
*
|
|
40
|
+
* **`--max-age`** (alpha.8, cuestión 5 del panel relay 2): el reporte lleva
|
|
41
|
+
* SIEMPRE la edad de la cabeza global de la cola (`queue.pendingAgeMs`, con
|
|
42
|
+
* el reloj de quien encoló) y se imprime; con umbral, superarlo es salida ≠ 0
|
|
43
|
+
* — la alarma que faltaba cuando el store está caído (un 503 no consume
|
|
44
|
+
* intento: `attempts` y `dead` se quedan a cero). Sin umbral solo informa.
|
|
15
45
|
*
|
|
16
46
|
* **`--only`** (alpha.5 · R1): releva solo los ids del recibo que devolvió
|
|
17
47
|
* `scopes.attached/moved/detached` (`{ queued }`). El orden por clave no se
|
|
@@ -58,6 +88,8 @@ export default class AuthzScopesRelay extends BaseCommand {
|
|
|
58
88
|
limit: number | undefined;
|
|
59
89
|
batchSize: number | undefined;
|
|
60
90
|
only: string[] | undefined;
|
|
91
|
+
waitLeaseMs: number | undefined;
|
|
92
|
+
maxAge: number | undefined;
|
|
61
93
|
/** `--only=12,13` o `--only=12 --only=13`: ids numéricos como número, el resto como cadena. */
|
|
62
94
|
static parseOnly(values: string[] | undefined): Array<string | number> | undefined;
|
|
63
95
|
run(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authz_scopes_relay.d.ts","sourceRoot":"","sources":["../../commands/authz_scopes_relay.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAS,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"authz_scopes_relay.d.ts","sourceRoot":"","sources":["../../commands/authz_scopes_relay.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAS,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,SAAS,GAAE,OAAe,GACzB;IAAE,KAAK,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAoC/F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,WAAW;IACvD,MAAM,CAAC,WAAW,SAAuB;IACzC,MAAM,CAAC,WAAW,SAAiF;IAEnG,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAMO,MAAM,EAAE,OAAO,GAAG,SAAS,CAAA;IAG3B,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IAGzB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAM7B,IAAI,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAM1B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IAM/B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAElC,+FAA+F;IAC/F,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,SAAS;IAS5E,GAAG;CAuGV"}
|
|
@@ -8,6 +8,56 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
10
|
import { BaseCommand, flags } from '@adonisjs/core/ace';
|
|
11
|
+
/**
|
|
12
|
+
* **La edad de la cola en el comando** (2.4.0-alpha.8; panel relay 2,
|
|
13
|
+
* cuestión 5), en una función PURA (patrón `requeueLines`): qué se imprime y
|
|
14
|
+
* si `--max-age` la convierte en salida ≠ 0.
|
|
15
|
+
*
|
|
16
|
+
* - Sin cabeza con fecha (`pendingAgeMs === null`: cola vacía, solo
|
|
17
|
+
* aparcadas, o una outbox de terceros sin `createdAt`) no se dice nada y
|
|
18
|
+
* nunca se alarma.
|
|
19
|
+
* - Con edad, se INFORMA siempre (es lo que faltaba en el B2 de COGNITIV:
|
|
20
|
+
* diez pasadas con el store caído y nadie sabía desde cuándo).
|
|
21
|
+
* - **Solo con umbral** (`--max-age=<ms>`) y edad POR ENCIMA es `exceeded`
|
|
22
|
+
* (exit ≠ 0): sin umbral un cron no puede quedarse en rojo para siempre
|
|
23
|
+
* por una cola que simplemente tiene backlog. Un umbral inválido lanza.
|
|
24
|
+
*/
|
|
25
|
+
export function queueAgeLines(queue, maxAgeMs, remaining = false) {
|
|
26
|
+
if (maxAgeMs !== undefined && !(Number.isInteger(maxAgeMs) && maxAgeMs >= 0)) {
|
|
27
|
+
throw new TypeError(`authz:scopes:relay: --max-age debe ser un entero >= 0 en milisegundos (llegó ${String(maxAgeMs)})`);
|
|
28
|
+
}
|
|
29
|
+
if (queue.pendingAgeMs === null || queue.oldestPendingAt === null) {
|
|
30
|
+
// «Cola vacía» y «no sé la edad» no son lo mismo (cierre alpha.8, 🟡 2 del
|
|
31
|
+
// auditor): con entradas pendientes y una outbox que no devuelve `createdAt`
|
|
32
|
+
// la alarma por edad no puede vigilar nada — y con `--max-age` eso es un
|
|
33
|
+
// fallo, no un silencio.
|
|
34
|
+
if (!remaining)
|
|
35
|
+
return { lines: [], exceeded: false };
|
|
36
|
+
const message = 'Hay entradas pendientes pero la outbox no devuelve `createdAt` (par de capacidad `createdAt` del contrato): ' +
|
|
37
|
+
'la edad de la cola es DESCONOCIDA y --max-age no puede vigilarla.';
|
|
38
|
+
return maxAgeMs === undefined
|
|
39
|
+
? { lines: [{ level: 'warning', message }], exceeded: false }
|
|
40
|
+
: { lines: [{ level: 'error', message }], exceeded: true };
|
|
41
|
+
}
|
|
42
|
+
const since = queue.oldestPendingAt.toISOString();
|
|
43
|
+
if (maxAgeMs !== undefined && queue.pendingAgeMs > maxAgeMs) {
|
|
44
|
+
return {
|
|
45
|
+
exceeded: true,
|
|
46
|
+
lines: [
|
|
47
|
+
{
|
|
48
|
+
level: 'error',
|
|
49
|
+
message: `La entrada pendiente más antigua de la cola lleva ${queue.pendingAgeMs} ms sin aplicarse (desde ${since}), ` +
|
|
50
|
+
`por encima de --max-age=${maxAgeMs}: la cola no avanza (¿el backend lleva caído desde entonces? un 503 no consume ` +
|
|
51
|
+
'intento y no aparca nada). Mira `failures` de esta pasada.',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
exceeded: false,
|
|
58
|
+
lines: [{ level: 'info', message: `Cabeza de la cola: pendiente desde ${since} (${queue.pendingAgeMs} ms; medida con el reloj de quien encoló).` }],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
11
61
|
/**
|
|
12
62
|
* Drena la OUTBOX del árbol de scopes y aplica los cambios al driver
|
|
13
63
|
* (3b-2d; panel 2, cruce 4 · S5).
|
|
@@ -20,6 +70,14 @@ import { BaseCommand, flags } from '@adonisjs/core/ace';
|
|
|
20
70
|
* node ace authz:scopes:relay --dry-run # solo dice qué haría
|
|
21
71
|
* node ace authz:scopes:relay --limit 500 # una pasada acotada
|
|
22
72
|
* node ace authz:scopes:relay --only=12,13 # SOLO esas entradas (2.4.0-alpha.5)
|
|
73
|
+
* node ace authz:scopes:relay --wait-lease-ms=5000 # espera el lease hasta 5 s en vez de salir `busy` (2.4.0-alpha.8)
|
|
74
|
+
* node ace authz:scopes:relay --max-age=300000 # exit ≠ 0 si la pendiente más antigua lleva > 5 min (2.4.0-alpha.8)
|
|
75
|
+
*
|
|
76
|
+
* **`--max-age`** (alpha.8, cuestión 5 del panel relay 2): el reporte lleva
|
|
77
|
+
* SIEMPRE la edad de la cabeza global de la cola (`queue.pendingAgeMs`, con
|
|
78
|
+
* el reloj de quien encoló) y se imprime; con umbral, superarlo es salida ≠ 0
|
|
79
|
+
* — la alarma que faltaba cuando el store está caído (un 503 no consume
|
|
80
|
+
* intento: `attempts` y `dead` se quedan a cero). Sin umbral solo informa.
|
|
23
81
|
*
|
|
24
82
|
* **`--only`** (alpha.5 · R1): releva solo los ids del recibo que devolvió
|
|
25
83
|
* `scopes.attached/moved/detached` (`{ queued }`). El orden por clave no se
|
|
@@ -87,7 +145,14 @@ export default class AuthzScopesRelay extends BaseCommand {
|
|
|
87
145
|
limit: this.limit,
|
|
88
146
|
batchSize: this.batchSize,
|
|
89
147
|
only,
|
|
148
|
+
...(this.waitLeaseMs === undefined ? {} : { waitForLeaseMs: this.waitLeaseMs }),
|
|
90
149
|
});
|
|
150
|
+
// La EDAD de la cola, siempre; con `--max-age`, la alarma (2.4.0-alpha.8).
|
|
151
|
+
const age = queueAgeLines(report.queue, this.maxAge, report.remaining);
|
|
152
|
+
for (const line of age.lines)
|
|
153
|
+
this.logger[line.level](line.message);
|
|
154
|
+
if (age.exceeded)
|
|
155
|
+
this.exitCode = 1;
|
|
91
156
|
const linea = (item) => {
|
|
92
157
|
const { change } = item;
|
|
93
158
|
const scope = (s) => `${s.type}:${s.uuid ?? ''}`;
|
|
@@ -186,4 +251,18 @@ __decorate([
|
|
|
186
251
|
}),
|
|
187
252
|
__metadata("design:type", Object)
|
|
188
253
|
], AuthzScopesRelay.prototype, "only", void 0);
|
|
254
|
+
__decorate([
|
|
255
|
+
flags.number({
|
|
256
|
+
name: 'wait-lease-ms',
|
|
257
|
+
description: 'Wait up to this many ms for the relay lease instead of reporting busy at once (default 0; needs a pool >= 2)',
|
|
258
|
+
}),
|
|
259
|
+
__metadata("design:type", Object)
|
|
260
|
+
], AuthzScopesRelay.prototype, "waitLeaseMs", void 0);
|
|
261
|
+
__decorate([
|
|
262
|
+
flags.number({
|
|
263
|
+
name: 'max-age',
|
|
264
|
+
description: 'Exit ≠ 0 if the oldest pending entry of the whole queue is older than this many ms (without it the age is only reported)',
|
|
265
|
+
}),
|
|
266
|
+
__metadata("design:type", Object)
|
|
267
|
+
], AuthzScopesRelay.prototype, "maxAge", void 0);
|
|
189
268
|
//# sourceMappingURL=authz_scopes_relay.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authz_scopes_relay.js","sourceRoot":"","sources":["../../commands/authz_scopes_relay.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"authz_scopes_relay.js","sourceRoot":"","sources":["../../commands/authz_scopes_relay.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,11 @@ import type { ScopeRequeueConflict, ScopeRequeueReport } from '../src/types.js';
|
|
|
12
12
|
* (que entra igual, con este mismo diff delante) y `authz:reconcile`.
|
|
13
13
|
* - `report`: se reencoló. Con `forced` y `overrides`, el diff se imprime
|
|
14
14
|
* como AVISO: forzar no es un éxito mudo.
|
|
15
|
+
* - `report.relay` (con `--apply`, 2.4.0-alpha.8): la pasada que relevó SOLO
|
|
16
|
+
* lo reencolado, bajo el mismo lease. `busy` ⇒ NO se reencoló nada (todo o
|
|
17
|
+
* nada) y se sale ≠ 0 con la letra («otra pasada tiene el lease; reintenta,
|
|
18
|
+
* o `requeue` sin `--apply`»); cada entrada dice si se aplicó, falló o quedó
|
|
19
|
+
* aplazada, y solo es limpio si TODO lo reencolado se aplicó.
|
|
15
20
|
*/
|
|
16
21
|
export declare function requeueLines(input: {
|
|
17
22
|
refused?: ScopeRequeueConflict[];
|
|
@@ -30,6 +35,8 @@ export declare function requeueLines(input: {
|
|
|
30
35
|
* node ace authz:scopes:requeue --id=12 # una
|
|
31
36
|
* node ace authz:scopes:requeue --id=12,13 # varias (o --id repetido)
|
|
32
37
|
* node ace authz:scopes:requeue --id=12 --force # aunque revierta lo aplicado después (diff impreso)
|
|
38
|
+
* node ace authz:scopes:requeue --id=12 --apply # y la aplica en la MISMA llamada (2.4.0-alpha.8)
|
|
39
|
+
* node ace authz:scopes:requeue --id=12 --apply --wait-lease-ms=5000 # esperando el lease si el cron lo tiene
|
|
33
40
|
*
|
|
34
41
|
* Una entrada aparcada (`sqlScopeOutbox({ maxAttempts })`, 5 por defecto)
|
|
35
42
|
* no se reintenta sola: el relay la reporta en cada pasada y sale ≠ 0. Cuando
|
|
@@ -47,6 +54,14 @@ export declare function requeueLines(input: {
|
|
|
47
54
|
* (decisión del dueño del 2026-09-09 (2)): imprime el diff y entra igual. Nunca
|
|
48
55
|
* en una ruta alcanzable por HTTP.
|
|
49
56
|
*
|
|
57
|
+
* **`--apply` (2.4.0-alpha.8)**: reencola Y releva SOLO esas entradas en la
|
|
58
|
+
* misma llamada, bajo UN lease (medido: sin esto la reencolada volvía con su
|
|
59
|
+
* id original y la siguiente pasada `only` del tenant salía `depends-on`; el
|
|
60
|
+
* incidente no se cerraba con un comando). Explícito, jamás por defecto: el
|
|
61
|
+
* comando pasa a ESCRIBIR en el backend. No compone con `--force` (500). Pasa
|
|
62
|
+
* la barrera del freeze. Con el lease tomado por otra pasada NO reencola nada
|
|
63
|
+
* y sale ≠ 0 (`--wait-lease-ms` espera, acotado).
|
|
64
|
+
*
|
|
50
65
|
* Es una operación de PLATAFORMA (como `authz:scopes:relay`): sin actor, sin
|
|
51
66
|
* `within`, y no se expone por HTTP.
|
|
52
67
|
*/
|
|
@@ -56,6 +71,8 @@ export default class AuthzScopesRequeue extends BaseCommand {
|
|
|
56
71
|
static options: CommandOptions;
|
|
57
72
|
id: string[] | undefined;
|
|
58
73
|
force: boolean | undefined;
|
|
74
|
+
apply: boolean | undefined;
|
|
75
|
+
waitLeaseMs: number | undefined;
|
|
59
76
|
/** `--id=12,13` o `--id=12 --id=13`: ids numéricos como número, el resto como cadena. */
|
|
60
77
|
static parseIds(values: string[] | undefined): Array<string | number> | undefined;
|
|
61
78
|
run(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authz_scopes_requeue.d.ts","sourceRoot":"","sources":["../../commands/authz_scopes_requeue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAS,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAA0B,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAYvG
|
|
1
|
+
{"version":3,"file":"authz_scopes_requeue.d.ts","sourceRoot":"","sources":["../../commands/authz_scopes_requeue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAS,MAAM,oBAAoB,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAA0B,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAYvG;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAAE,OAAO,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,kBAAkB,CAAA;CAAE,GAAG;IACtG,KAAK,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,KAAK,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACjF,KAAK,EAAE,OAAO,CAAA;CACf,CAqFA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,CAAC,OAAO,OAAO,kBAAmB,SAAQ,WAAW;IACzD,MAAM,CAAC,WAAW,SAAyB;IAC3C,MAAM,CAAC,WAAW,SAAkH;IAEpI,MAAM,CAAC,OAAO,EAAE,cAAc,CAE7B;IAMO,EAAE,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAKxB,KAAK,EAAE,OAAO,GAAG,SAAS,CAAA;IAK1B,KAAK,EAAE,OAAO,GAAG,SAAS,CAAA;IAM1B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IAEvC,yFAAyF;IACzF,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,SAAS;IAS3E,GAAG;CAuBV"}
|
|
@@ -28,6 +28,11 @@ function linea(item) {
|
|
|
28
28
|
* (que entra igual, con este mismo diff delante) y `authz:reconcile`.
|
|
29
29
|
* - `report`: se reencoló. Con `forced` y `overrides`, el diff se imprime
|
|
30
30
|
* como AVISO: forzar no es un éxito mudo.
|
|
31
|
+
* - `report.relay` (con `--apply`, 2.4.0-alpha.8): la pasada que relevó SOLO
|
|
32
|
+
* lo reencolado, bajo el mismo lease. `busy` ⇒ NO se reencoló nada (todo o
|
|
33
|
+
* nada) y se sale ≠ 0 con la letra («otra pasada tiene el lease; reintenta,
|
|
34
|
+
* o `requeue` sin `--apply`»); cada entrada dice si se aplicó, falló o quedó
|
|
35
|
+
* aplazada, y solo es limpio si TODO lo reencolado se aplicó.
|
|
31
36
|
*/
|
|
32
37
|
export function requeueLines(input) {
|
|
33
38
|
const lines = [];
|
|
@@ -59,17 +64,63 @@ export function requeueLines(input) {
|
|
|
59
64
|
return { lines, clean: false };
|
|
60
65
|
}
|
|
61
66
|
const report = input.report;
|
|
67
|
+
if (report.relay?.busy) {
|
|
68
|
+
lines.push({
|
|
69
|
+
level: 'error',
|
|
70
|
+
message: 'Otra pasada del relay tiene el lease de la cola: con --apply no se ha reencolado NADA (todo o nada: reencolar y no ' +
|
|
71
|
+
'aplicar dejaría la entrada con un solo intento, que la siguiente pasada gastaría). Reintenta (--wait-lease-ms espera ' +
|
|
72
|
+
'a que la suelten), o reencola sin --apply y deja que la próxima pasada la aplique.',
|
|
73
|
+
});
|
|
74
|
+
return { lines, clean: false };
|
|
75
|
+
}
|
|
62
76
|
if (report.forced)
|
|
63
77
|
for (const conflict of report.overrides)
|
|
64
78
|
diff('warning', conflict);
|
|
65
79
|
for (const item of report.requeued)
|
|
66
80
|
lines.push({ level: 'log', message: `reencolada: ${linea(item)}` });
|
|
81
|
+
if (report.relay === undefined) {
|
|
82
|
+
lines.push({
|
|
83
|
+
level: 'success',
|
|
84
|
+
message: `${report.requeued.length} entrada(s) devuelta(s) a la cola con attempts = maxAttempts − 1: si la causa sigue rota ` +
|
|
85
|
+
'vuelve(n) a aparcarse en UNA pasada; si la arreglaste, entra(n) a la primera. Corre node ace authz:scopes:relay.',
|
|
86
|
+
});
|
|
87
|
+
return { lines, clean: true };
|
|
88
|
+
}
|
|
89
|
+
// --apply: qué fue de cada reencolada en la pasada que corrió bajo el mismo lease.
|
|
90
|
+
const relay = report.relay;
|
|
91
|
+
let clean = true;
|
|
92
|
+
for (const entry of relay.entries) {
|
|
93
|
+
if (entry.status === 'applied')
|
|
94
|
+
lines.push({ level: 'log', message: `aplicada: ${linea(entry)}` });
|
|
95
|
+
else if (entry.status === 'deferred') {
|
|
96
|
+
clean = false;
|
|
97
|
+
lines.push({ level: 'warning', message: `aplazada (${entry.reason ?? 'depende de una entrada anterior'}): ${linea(entry)} — no se ha aplicado` });
|
|
98
|
+
}
|
|
99
|
+
else if (entry.status === 'failed') {
|
|
100
|
+
clean = false;
|
|
101
|
+
lines.push({ level: 'error', message: `falló: ${linea(entry)} — ${entry.reason ?? ''}` });
|
|
102
|
+
}
|
|
103
|
+
else if (entry.status === 'dead') {
|
|
104
|
+
clean = false;
|
|
105
|
+
lines.push({ level: 'error', message: `sigue APARCADA: ${linea(entry)}${entry.reason ? ` — ${entry.reason}` : ''}` });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (relay.applied.length !== report.requeued.length)
|
|
109
|
+
clean = false;
|
|
110
|
+
if (relay.remaining) {
|
|
111
|
+
lines.push({
|
|
112
|
+
level: 'warning',
|
|
113
|
+
message: 'Quedan entradas pendientes en la cola (--apply releva SOLO lo reencolado; lo que dependía de ello sigue aplazado): ' +
|
|
114
|
+
'corre node ace authz:scopes:relay, o releva su recibo con --only.',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
67
117
|
lines.push({
|
|
68
|
-
level: 'success',
|
|
69
|
-
message:
|
|
70
|
-
|
|
118
|
+
level: clean ? 'success' : 'warning',
|
|
119
|
+
message: clean
|
|
120
|
+
? `${relay.applied.length} entrada(s) reencolada(s) y aplicada(s) en la misma pasada.`
|
|
121
|
+
: `${relay.applied.length} de ${report.requeued.length} reencolada(s) aplicada(s): mira las líneas de arriba (la causa sigue rota, o el backend no respondió).`,
|
|
71
122
|
});
|
|
72
|
-
return { lines, clean
|
|
123
|
+
return { lines, clean };
|
|
73
124
|
}
|
|
74
125
|
/**
|
|
75
126
|
* **Devuelve entradas APARCADAS de la outbox a la cola** (2.4.0-alpha.5 · R2b;
|
|
@@ -78,6 +129,8 @@ export function requeueLines(input) {
|
|
|
78
129
|
* node ace authz:scopes:requeue --id=12 # una
|
|
79
130
|
* node ace authz:scopes:requeue --id=12,13 # varias (o --id repetido)
|
|
80
131
|
* node ace authz:scopes:requeue --id=12 --force # aunque revierta lo aplicado después (diff impreso)
|
|
132
|
+
* node ace authz:scopes:requeue --id=12 --apply # y la aplica en la MISMA llamada (2.4.0-alpha.8)
|
|
133
|
+
* node ace authz:scopes:requeue --id=12 --apply --wait-lease-ms=5000 # esperando el lease si el cron lo tiene
|
|
81
134
|
*
|
|
82
135
|
* Una entrada aparcada (`sqlScopeOutbox({ maxAttempts })`, 5 por defecto)
|
|
83
136
|
* no se reintenta sola: el relay la reporta en cada pasada y sale ≠ 0. Cuando
|
|
@@ -95,6 +148,14 @@ export function requeueLines(input) {
|
|
|
95
148
|
* (decisión del dueño del 2026-09-09 (2)): imprime el diff y entra igual. Nunca
|
|
96
149
|
* en una ruta alcanzable por HTTP.
|
|
97
150
|
*
|
|
151
|
+
* **`--apply` (2.4.0-alpha.8)**: reencola Y releva SOLO esas entradas en la
|
|
152
|
+
* misma llamada, bajo UN lease (medido: sin esto la reencolada volvía con su
|
|
153
|
+
* id original y la siguiente pasada `only` del tenant salía `depends-on`; el
|
|
154
|
+
* incidente no se cerraba con un comando). Explícito, jamás por defecto: el
|
|
155
|
+
* comando pasa a ESCRIBIR en el backend. No compone con `--force` (500). Pasa
|
|
156
|
+
* la barrera del freeze. Con el lease tomado por otra pasada NO reencola nada
|
|
157
|
+
* y sale ≠ 0 (`--wait-lease-ms` espera, acotado).
|
|
158
|
+
*
|
|
98
159
|
* Es una operación de PLATAFORMA (como `authz:scopes:relay`): sin actor, sin
|
|
99
160
|
* `within`, y no se expone por HTTP.
|
|
100
161
|
*/
|
|
@@ -124,7 +185,11 @@ export default class AuthzScopesRequeue extends BaseCommand {
|
|
|
124
185
|
const { default: authorization } = await import('../services/main.js');
|
|
125
186
|
let result;
|
|
126
187
|
try {
|
|
127
|
-
const report = await authorization.requeueScopeChanges(ids, {
|
|
188
|
+
const report = await authorization.requeueScopeChanges(ids, {
|
|
189
|
+
force: this.force === true,
|
|
190
|
+
apply: this.apply === true,
|
|
191
|
+
...(this.waitLeaseMs === undefined ? {} : { waitForLeaseMs: this.waitLeaseMs }),
|
|
192
|
+
});
|
|
128
193
|
result = requeueLines({ report });
|
|
129
194
|
}
|
|
130
195
|
catch (error) {
|
|
@@ -151,4 +216,17 @@ __decorate([
|
|
|
151
216
|
}),
|
|
152
217
|
__metadata("design:type", Object)
|
|
153
218
|
], AuthzScopesRequeue.prototype, "force", void 0);
|
|
219
|
+
__decorate([
|
|
220
|
+
flags.boolean({
|
|
221
|
+
description: 'Also relay ONLY the requeued entries in the same call, under one lease (writes to the backend; not with --force)',
|
|
222
|
+
}),
|
|
223
|
+
__metadata("design:type", Object)
|
|
224
|
+
], AuthzScopesRequeue.prototype, "apply", void 0);
|
|
225
|
+
__decorate([
|
|
226
|
+
flags.number({
|
|
227
|
+
name: 'wait-lease-ms',
|
|
228
|
+
description: 'With --apply: wait up to this many ms for the relay lease instead of failing at once (pool >= 2)',
|
|
229
|
+
}),
|
|
230
|
+
__metadata("design:type", Object)
|
|
231
|
+
], AuthzScopesRequeue.prototype, "waitLeaseMs", void 0);
|
|
154
232
|
//# sourceMappingURL=authz_scopes_requeue.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authz_scopes_requeue.js","sourceRoot":"","sources":["../../commands/authz_scopes_requeue.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAIvD,sFAAsF;AACtF,SAAS,KAAK,CAAC,IAA4B;IACzC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;IACvB,MAAM,KAAK,GAAG,CAAC,CAAyC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,CAAA;IACxF,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAA;IAC5E,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;IACrE,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAC1D,OAAO,IAAI,IAAI,CAAC,EAAE,IAAI,MAAM,CAAC,EAAE,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,EAAE,CAAA;AACvF,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"authz_scopes_requeue.js","sourceRoot":"","sources":["../../commands/authz_scopes_requeue.ts"],"names":[],"mappings":"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"}
|
package/build/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Entrada pública del paquete.
|
|
3
3
|
* import { AuthorizationManager, APP_SCOPE } from '@jantstack/adonis-authz'
|
|
4
4
|
*/
|
|
5
|
-
export { AuthorizationManager, DEFAULT_MAX_SCOPES, DEFAULT_MAX_DESCENDANTS, MAX_SCOPE_BOUND, DEFAULT_VIEW_MAX_AGE_MS, PARKED_CENSUS_MAX, RELAY_ORIGIN_MAX_HOPS, expandExcludedSubtrees } from './src/manager.js';
|
|
5
|
+
export { AuthorizationManager, DEFAULT_MAX_SCOPES, DEFAULT_MAX_DESCENDANTS, MAX_SCOPE_BOUND, DEFAULT_VIEW_MAX_AGE_MS, PARKED_CENSUS_MAX, RELAY_ORIGIN_MAX_HOPS, RELAY_LEASE_POLL_MS, expandExcludedSubtrees } from './src/manager.js';
|
|
6
6
|
export type { AuthorizationView, ForRequestOptions } from './src/manager.js';
|
|
7
7
|
export { defineConfig } from './src/define_config.js';
|
|
8
8
|
export type { AuthorizationConfig } from './src/define_config.js';
|