@authorbot/domain 0.1.0

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/dist/vote.js ADDED
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ import { uuidv7Schema } from "@authorbot/schemas";
3
+ import { ALLOWED, denied } from "./decision.js";
4
+ /**
5
+ * Vote commands (Phase 3 contract section 2):
6
+ * `PUT /v1/projects/{p}/annotations/{id}/vote` with `{ value }`, `DELETE`
7
+ * clears. One current vote per actor is a DB uniqueness concern (upsert);
8
+ * this module pins the command shapes and the suggestion-only rule.
9
+ */
10
+ /** Vote values (contract section 2; mirrors `authorbot.instance/v1` `votes.values`). */
11
+ export const VOTE_VALUES = ["approve", "reject", "abstain"];
12
+ export const voteValueSchema = z.enum(VOTE_VALUES);
13
+ /** `PUT .../annotations/{annotationId}/vote` — route param + body merged. */
14
+ export const castVoteCommandSchema = z.strictObject({
15
+ annotationId: uuidv7Schema,
16
+ value: voteValueSchema,
17
+ });
18
+ /** `DELETE .../annotations/{annotationId}/vote` — the command is the route param. */
19
+ export const clearVoteCommandSchema = z.strictObject({
20
+ annotationId: uuidv7Schema,
21
+ });
22
+ /**
23
+ * Suggestion-only guard (contract section 2: votes on comments → 422). Votes
24
+ * stay legal after `open` — sticky decisions require tracking vote changes on
25
+ * `work_item_created` annotations (contract section 4) — so annotation status
26
+ * is deliberately not checked here; `votes:write` scope enforcement is the
27
+ * API layer's (`requireScope`).
28
+ */
29
+ export function authorizeVote(input) {
30
+ if (input.annotationKind !== "suggestion") {
31
+ return denied("not-a-suggestion", "votes apply to suggestions only; comments cannot be voted on");
32
+ }
33
+ return ALLOWED;
34
+ }
35
+ //# sourceMappingURL=vote.js.map
@@ -0,0 +1,41 @@
1
+ import { type WorkItemStatus } from "@authorbot/schemas";
2
+ import { type Decision, type Denied } from "./decision.js";
3
+ /**
4
+ * Phase 4 work-item lifecycle: the full design section 9.5 machine with the
5
+ * Phase 3 gate lifted (Phase 4 contract sections 2, 4, 5). The graph itself
6
+ * has been declared since Phase 3 (`WORK_ITEM_TRANSITIONS`); this module
7
+ * adds the executable, trigger-labelled form. `transitionWorkItem` (the
8
+ * Phase 3-gated function) is left untouched for compatibility — Phase 4
9
+ * call sites use `transitionWorkItemPhase4` / `applyWorkItemTrigger`.
10
+ */
11
+ /**
12
+ * The design section 9.5 edge labels. One trigger per labelled arrow;
13
+ * `expire` and `release` are the two expiry/release back-edges sharing
14
+ * `leased -> ready`, and `cancel` covers both maintainer-action arrows.
15
+ */
16
+ export declare const WORK_ITEM_TRIGGERS: readonly ["claim", "expire", "release", "submit", "validation_passed", "validation_failed", "apply_succeeded", "apply_conflicted", "conflict_resolution_prepared", "cancel"];
17
+ export type WorkItemTrigger = (typeof WORK_ITEM_TRIGGERS)[number];
18
+ /** Trigger -> the design edges it may traverse (exactly section 9.5). */
19
+ export declare const WORK_ITEM_TRIGGER_EDGES: Readonly<Record<WorkItemTrigger, ReadonlyArray<readonly [WorkItemStatus, WorkItemStatus]>>>;
20
+ /** Statuses with no outgoing edges (design section 9.5). */
21
+ export declare function isWorkItemTerminal(status: WorkItemStatus): boolean;
22
+ export type WorkItemLifecycleDenialReason = "illegal-transition";
23
+ /**
24
+ * Phase 4 edge check: every design section 9.5 edge is executable, nothing
25
+ * else is. (Contrast `transitionWorkItem`, which additionally applies the
26
+ * Phase 3 gate.)
27
+ */
28
+ export declare function transitionWorkItemPhase4(from: WorkItemStatus, to: WorkItemStatus): Decision<WorkItemLifecycleDenialReason>;
29
+ export type WorkItemTriggerResult = {
30
+ readonly allowed: true;
31
+ readonly next: WorkItemStatus;
32
+ } | Denied<WorkItemLifecycleDenialReason>;
33
+ /**
34
+ * Apply a labelled trigger to a status. Deterministic: every trigger maps a
35
+ * given status to at most one next status, so commands can ask "what does
36
+ * `expire` do to this item?" without hand-picking the target state.
37
+ */
38
+ export declare function applyWorkItemTrigger(status: WorkItemStatus, trigger: WorkItemTrigger): WorkItemTriggerResult;
39
+ /** All statuses are live in Phase 4 (the Phase 3 stop-at-ready gate is lifted). */
40
+ export declare const PHASE4_WORK_ITEM_STATUSES: readonly ["ready", "leased", "submitted", "applying", "completed", "conflict", "failed", "cancelled"];
41
+ //# sourceMappingURL=work-item-lifecycle.d.ts.map
@@ -0,0 +1,75 @@
1
+ import { WORK_ITEM_STATUSES } from "@authorbot/schemas";
2
+ import { ALLOWED, denied } from "./decision.js";
3
+ import { WORK_ITEM_TRANSITIONS, canTransitionWorkItem } from "./work-item-state.js";
4
+ /**
5
+ * Phase 4 work-item lifecycle: the full design section 9.5 machine with the
6
+ * Phase 3 gate lifted (Phase 4 contract sections 2, 4, 5). The graph itself
7
+ * has been declared since Phase 3 (`WORK_ITEM_TRANSITIONS`); this module
8
+ * adds the executable, trigger-labelled form. `transitionWorkItem` (the
9
+ * Phase 3-gated function) is left untouched for compatibility — Phase 4
10
+ * call sites use `transitionWorkItemPhase4` / `applyWorkItemTrigger`.
11
+ */
12
+ /**
13
+ * The design section 9.5 edge labels. One trigger per labelled arrow;
14
+ * `expire` and `release` are the two expiry/release back-edges sharing
15
+ * `leased -> ready`, and `cancel` covers both maintainer-action arrows.
16
+ */
17
+ export const WORK_ITEM_TRIGGERS = [
18
+ "claim",
19
+ "expire",
20
+ "release",
21
+ "submit",
22
+ "validation_passed",
23
+ "validation_failed",
24
+ "apply_succeeded",
25
+ "apply_conflicted",
26
+ "conflict_resolution_prepared",
27
+ "cancel",
28
+ ];
29
+ /** Trigger -> the design edges it may traverse (exactly section 9.5). */
30
+ export const WORK_ITEM_TRIGGER_EDGES = Object.freeze({
31
+ claim: [["ready", "leased"]],
32
+ expire: [["leased", "ready"]],
33
+ release: [["leased", "ready"]],
34
+ submit: [["leased", "submitted"]],
35
+ validation_passed: [["submitted", "applying"]],
36
+ validation_failed: [["submitted", "failed"]],
37
+ apply_succeeded: [["applying", "completed"]],
38
+ apply_conflicted: [["applying", "conflict"]],
39
+ conflict_resolution_prepared: [["conflict", "ready"]],
40
+ cancel: [
41
+ ["ready", "cancelled"],
42
+ ["leased", "cancelled"],
43
+ ],
44
+ });
45
+ /** Statuses with no outgoing edges (design section 9.5). */
46
+ export function isWorkItemTerminal(status) {
47
+ return WORK_ITEM_TRANSITIONS[status].length === 0;
48
+ }
49
+ /**
50
+ * Phase 4 edge check: every design section 9.5 edge is executable, nothing
51
+ * else is. (Contrast `transitionWorkItem`, which additionally applies the
52
+ * Phase 3 gate.)
53
+ */
54
+ export function transitionWorkItemPhase4(from, to) {
55
+ if (!canTransitionWorkItem(from, to)) {
56
+ return denied("illegal-transition", `work item status cannot change from "${from}" to "${to}"`);
57
+ }
58
+ return ALLOWED;
59
+ }
60
+ /**
61
+ * Apply a labelled trigger to a status. Deterministic: every trigger maps a
62
+ * given status to at most one next status, so commands can ask "what does
63
+ * `expire` do to this item?" without hand-picking the target state.
64
+ */
65
+ export function applyWorkItemTrigger(status, trigger) {
66
+ for (const [from, to] of WORK_ITEM_TRIGGER_EDGES[trigger]) {
67
+ if (from === status) {
68
+ return { allowed: true, next: to };
69
+ }
70
+ }
71
+ return denied("illegal-transition", `trigger "${trigger}" does not apply to a work item in status "${status}"`);
72
+ }
73
+ /** All statuses are live in Phase 4 (the Phase 3 stop-at-ready gate is lifted). */
74
+ export const PHASE4_WORK_ITEM_STATUSES = WORK_ITEM_STATUSES;
75
+ //# sourceMappingURL=work-item-lifecycle.js.map
@@ -0,0 +1,28 @@
1
+ import { WORK_ITEM_STATUSES, type WorkItemStatus } from "@authorbot/schemas";
2
+ import { type Decision } from "./decision.js";
3
+ export { WORK_ITEM_STATUSES };
4
+ export type { WorkItemStatus };
5
+ /**
6
+ * Work-item state machine (design section 9.5). The full transition graph is
7
+ * declared so Phase 4 is additive data, but Phase 3 stops at `ready` (Phase 3
8
+ * contract section 1: claims/leases/submissions are out of scope) — the only
9
+ * transition that may actually execute is `ready -> cancelled` (maintainer
10
+ * cancel). Every other graph edge is phase-gated: legal in the design, denied
11
+ * at runtime with `phase-not-enabled` until Phase 4 lifts the gate.
12
+ */
13
+ export declare const WORK_ITEM_TRANSITIONS: Readonly<Record<WorkItemStatus, readonly WorkItemStatus[]>>;
14
+ /** Statuses a work item can actually hold in Phase 3 (contract section 1). */
15
+ export declare const PHASE3_WORK_ITEM_STATUSES: readonly ["ready", "cancelled"];
16
+ export declare function isPhase3WorkItemStatus(status: WorkItemStatus): boolean;
17
+ /** Whether the full design section 9.5 graph has this edge (phase-agnostic). */
18
+ export declare function canTransitionWorkItem(from: WorkItemStatus, to: WorkItemStatus): boolean;
19
+ export type WorkItemTransitionDenialReason = "illegal-transition" | "phase-not-enabled";
20
+ /**
21
+ * Typed decision for a requested work-item status change under the Phase 3
22
+ * gate: the edge must exist in the design graph AND both endpoints must be
23
+ * Phase 3 statuses. Graph-legal but gated edges (e.g. `ready -> leased`)
24
+ * are denied with `phase-not-enabled` so callers can distinguish "never
25
+ * legal" from "not yet".
26
+ */
27
+ export declare function transitionWorkItem(from: WorkItemStatus, to: WorkItemStatus): Decision<WorkItemTransitionDenialReason>;
28
+ //# sourceMappingURL=work-item-state.d.ts.map
@@ -0,0 +1,47 @@
1
+ import { WORK_ITEM_STATUSES } from "@authorbot/schemas";
2
+ import { ALLOWED, denied } from "./decision.js";
3
+ export { WORK_ITEM_STATUSES };
4
+ /**
5
+ * Work-item state machine (design section 9.5). The full transition graph is
6
+ * declared so Phase 4 is additive data, but Phase 3 stops at `ready` (Phase 3
7
+ * contract section 1: claims/leases/submissions are out of scope) — the only
8
+ * transition that may actually execute is `ready -> cancelled` (maintainer
9
+ * cancel). Every other graph edge is phase-gated: legal in the design, denied
10
+ * at runtime with `phase-not-enabled` until Phase 4 lifts the gate.
11
+ */
12
+ export const WORK_ITEM_TRANSITIONS = Object.freeze({
13
+ ready: ["leased", "cancelled"],
14
+ leased: ["ready", "submitted", "cancelled"],
15
+ submitted: ["applying", "failed"],
16
+ applying: ["completed", "conflict"],
17
+ conflict: ["ready"],
18
+ completed: [],
19
+ failed: [],
20
+ cancelled: [],
21
+ });
22
+ /** Statuses a work item can actually hold in Phase 3 (contract section 1). */
23
+ export const PHASE3_WORK_ITEM_STATUSES = ["ready", "cancelled"];
24
+ export function isPhase3WorkItemStatus(status) {
25
+ return PHASE3_WORK_ITEM_STATUSES.includes(status);
26
+ }
27
+ /** Whether the full design section 9.5 graph has this edge (phase-agnostic). */
28
+ export function canTransitionWorkItem(from, to) {
29
+ return WORK_ITEM_TRANSITIONS[from].includes(to);
30
+ }
31
+ /**
32
+ * Typed decision for a requested work-item status change under the Phase 3
33
+ * gate: the edge must exist in the design graph AND both endpoints must be
34
+ * Phase 3 statuses. Graph-legal but gated edges (e.g. `ready -> leased`)
35
+ * are denied with `phase-not-enabled` so callers can distinguish "never
36
+ * legal" from "not yet".
37
+ */
38
+ export function transitionWorkItem(from, to) {
39
+ if (!canTransitionWorkItem(from, to)) {
40
+ return denied("illegal-transition", `work item status cannot change from "${from}" to "${to}"`);
41
+ }
42
+ if (!isPhase3WorkItemStatus(from) || !isPhase3WorkItemStatus(to)) {
43
+ return denied("phase-not-enabled", `work item transition "${from}" -> "${to}" is deferred to Phase 4 (work items stop at "ready" in Phase 3)`);
44
+ }
45
+ return ALLOWED;
46
+ }
47
+ //# sourceMappingURL=work-item-state.js.map
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@authorbot/domain",
3
+ "version": "0.1.0",
4
+ "description": "Pure domain logic for Authorbot: role/scope authorization, annotation and Git-operation state machines, command validators, and token/session value rules (Phase 2 contract section 3)",
5
+ "keywords": [
6
+ "authorbot",
7
+ "domain",
8
+ "authorization"
9
+ ],
10
+ "license": "MIT",
11
+ "author": "Joe Mattie",
12
+ "homepage": "https://github.com/JoeMattie/authorbot#readme",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/JoeMattie/authorbot.git",
16
+ "directory": "packages/domain"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/JoeMattie/authorbot/issues"
20
+ },
21
+ "type": "module",
22
+ "publishConfig": {
23
+ "access": "public",
24
+ "provenance": true
25
+ },
26
+ "engines": {
27
+ "node": ">=22"
28
+ },
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "default": "./dist/index.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "LICENSE",
38
+ "!dist/**/*.map"
39
+ ],
40
+ "dependencies": {
41
+ "zod": "^4.0.0",
42
+ "@authorbot/schemas": "0.1.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^22.0.0",
46
+ "typescript": "^5.6.0",
47
+ "vitest": "^3.0.0"
48
+ },
49
+ "scripts": {
50
+ "build": "tsc -p tsconfig.build.json",
51
+ "test": "vitest run --testTimeout=30000",
52
+ "typecheck": "tsc --noEmit"
53
+ }
54
+ }