Populates the five real problem-management stubs (investigation,
root-causes, solutions, verification, resolutions -- problems is
confirmed dead/unwired scaffold and stays untouched) with doc04's
sequential workflow engine:
- investigation: version-row-per-attempt (never overwritten), with a
customer-safe read path that always strips internalNotes.
- root-causes/solutions/verification: a strict existence chain
(investigation -> root cause -> solution -> approval -> implementation
-> verification), each step resolve-or-409 on its own precondition,
matching doc06's schema field-for-field with no invented columns.
- resolutions: gated on a successfully verified solution (no stored
solutionId FK, per doc06 -- resolved via a join at write time), moving
the ticket to RESOLUTION_PENDING_CUSTOMER; explicit customer
confirmation and a durable auto-close sweep (the previously-unregistered
CLEANUP queue stub, mirroring 008's breach-detection job) both resolve
it from there.
- reopen (ticketing/tickets): two real, separately-audited transitions
(RESOLVED|CLOSED -> REOPENED -> IN_PROGRESS), touching no prior
problem-resolution record and no SLARun -- closes the loop 008's own
spec.md left open.
Verification-failure escalation reuses 003/007's existing
HUMAN_ESCALATION transition directly rather than adding an eleventh
trigger type to 008's already-shipped escalation rules.
Customer-facing confirm-resolution/reopen needed a body-shape variant of
002's inbound trust boundary that didn't previously exist:
fastify.authenticateProductIntegration hard-required a full
ticket-creation-shaped body. Extracted the shared token/scope/replay
verification into verifyIntegrationIdentity and added a narrower
authenticateProductIntegrationIdentity decorator + identityOnlyRequestSchema
on top of it -- purely additive, ticket creation's own behavior is
unchanged.
Also fixes a real test-data-hygiene bug surfaced by running this
feature's suite alongside 008's: a wildcard-scoped HierarchyNode and an
intentionally-global SLAPolicy in 008's own test fixtures were silently
affecting other test files' tickets sharing the same live Postgres.
Verified against throwaway Docker Postgres/Redis: typecheck, lint,
architecture-check all clean; full regression (tests/unit +
tests/integration together, 172 tests) passes except the 2 pre-existing
MinIO-dependent attachment failures, unrelated to this feature.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
49 tasks across 9 phases (6 user stories + setup/foundational/polish).
Unlike 007/008, this feature's user stories are genuinely sequential
(doc04's own investigation -> root cause -> solution -> verification ->
resolution -> reopen chain), so each story's dependency on the last is
real, not just priority-driven ordering -- called out explicitly since
User Story 4 (P2, verification) is a structural prerequisite of User
Story 5 (P1, resolution) despite the lower priority label.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 0 research resolves module placement (problem-management/problems
confirmed dead/unwired, left untouched), Investigation's version-row-per-
attempt shape, the strict investigation->root-cause->solution->
implementation->verification existence chain, why Resolution has no
solutionId FK (matches doc06 exactly), why verification-failure
escalation reuses 003/007's plain HUMAN_ESCALATION transition instead of
adding an eleventh trigger type to 008's already-shipped escalation
rules, the customer-facing route design (reusing 002's inbound trust
boundary rather than fastify.authenticate), and the auto-close sweep
design (the already-defined-but-unused CLEANUP queue, mirroring 008's
breach-detection job).
Phase 1 adds data-model.md, the admin/customer-facing contract, and six
quickstart scenarios covering the full sequential workflow through
customer confirmation, auto-close, and reopen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Investigation -> Root Cause -> Solution -> Implementation ->
Verification -> Resolution, plus customer confirmation and reopen,
matching doc04 SS3-9's workflow narrative and doc06's "Domain: Problem
Resolution" schema exactly. Explicitly closes a loop 008's own spec left
open (reopen never restarts an SLA run); verification-failure escalation
reuses 003/007's existing HUMAN_ESCALATION transition rather than adding
an eleventh escalation-rule trigger type to 008's system.
Also documents a real scope-boundary finding: src/modules/problem-management/problems
is a dead, unwired duplicate scaffold for Problem (the real one has lived
in ticketing/tickets since 003) and is not touched by this feature -- only
the five investigation/root-causes/solutions/resolutions/verification
stub directories are.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Populates platform/business-calendars, orchestration/sla, and
orchestration/escalation (all thin stubs until now) with the real engine:
- business-calendars: a luxon-based day-by-day calendar walk
(addBusinessMinutes/isWithinWorkingHours) excluding non-working hours,
weekends, and holidays — replacing the naive createdAt+hours stub FR-004
explicitly forbids.
- sla: most-specific SLAPolicy resolution (product/category/problemType/
priority, wildcard-or-exact-match, specificity-count + updatedAt
tiebreak), SLARun creation on the first real publish of the
long-unused TICKET_ASSIGNED domain event, durable pause/resume via an
absolute-timestamp shift (no in-memory state, verified across a real
buildApp() restart), and a repeatable BullMQ breach-detection sweep
(src/jobs/sla, itself a previously-unregistered stub) that is directly
callable for tests, not only reachable through a running worker.
- escalation: EscalationPolicy/Rule CRUD (all 10 doc05 trigger types
storable, only resolution_breach/first_response_breach evaluated),
breach-triggered and manual escalation both funnel through one EscalationEvent
+ scoped re-assignment path. AssignmentEngine (007) gains
assignToSpecificNode — a new, explicitly node-scoped entry point,
since escalation must never let 007's general resolution re-derive a
different node than the one a rule or a caller targeted.
Two small pre-existing scaffold gaps were closed along the way:
CategoriesRepository had no findById, and TICKET_ASSIGNED/SLA_BREACHED/
ESCALATION_TRIGGERED were defined since earlier phases but never
published by any code.
Verified against throwaway Docker Postgres/Redis (typecheck, lint,
architecture-check all clean; 148/150 relevant tests pass — the 2
failures are pre-existing, MinIO-dependent, and unrelated to this
feature).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
48 tasks across 9 phases (6 user stories + setup/foundational/polish),
sequenced US1 (policy definition) -> US2 (calendar-aware run creation) ->
US3 (durable pause/resume, P1-complete MVP) -> US4 (breach detection) ->
US5 (breach-triggered escalation) -> US6 (manual escalation), each
dependent on the last since every story builds on the previous one's
mechanism rather than being independently orderable.
Also folds in two design refinements found while cross-checking the
existing scaffold against research.md's plan: TICKET_ASSIGNED (defined in
domain-events.ts since 007, never published) becomes the real wiring point
for SLA-run creation, and src/jobs/sla//src/jobs/escalation/ turn out to
already exist as their own stub scaffolding, reused rather than
duplicated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 0 research resolves the business-calendar working-hours algorithm
(day-by-day walk via luxon, the first date/timezone dependency in this
codebase), the workingHours JSON shape, most-specific SLA-policy match
(reusing 005/006's resolution pattern), durable pause/resume (absolute
due-date shift, no in-memory state), and a repeatable-job breach-detection
design over per-run delayed jobs. Phase 1 adds data-model.md (one additive
refinement beyond doc06: SLARun.firstResponseBreachedAt), the admin/read
contract, and six quickstart scenarios including a genuine process-restart
boundary test for Constitution Principle VII.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 8 of the roadmap: SLA policy engine (most-specific-match, calendar-
aware due dates), durable pause/resume and breach detection (never an
in-memory timer), and a rule-driven escalation engine that re-assigns via
007's engine scoped to a specific target node. Bounded to the two
SLA-derived trigger types this feature can compute a real signal for;
the other eight doc 05 §6 trigger types remain valid rule configuration
without a wired event source yet. First feature to give 006's
HierarchyNode.slaPolicyId/escalationPolicyId fields a real target.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 7 of the roadmap. On a ticket's automatic transition to
HUMAN_ESCALATION, routing resolves 006's hierarchy nodes and
capability-eligibility lookup directly (never a second matching
algorithm), and a pluggable strategy (ROUND_ROBIN, concurrency-safe via
atomic Redis INCR; LEAST_LOADED; SKILL_BASED) selects exactly one
eligible agent, persisted as a version-row-per-period Assignment plus an
append-only AssignmentHistory event log. MANUAL/DIRECT are never
auto-selected — only an explicit admin-supplied agentId reaches them.
On success the ticket moves HUMAN_ESCALATION -> IN_PROGRESS through
003's existing state machine. A ticket's "required skill" comes from
its most recent AI diagnosis's problemType (005) when one exists,
unioned with any matching hierarchy node's skills (006); when neither
exists, there's no skill constraint (every active agent eligible),
never zero.
Found and fixed two real, latent bugs in the shared event-bus
infrastructure while building this feature's own tests: (1)
EventBus.publish was built on EventEmitter.emit(), which never awaits
async listeners, so a caller had no guarantee any subscriber (005's
AI-session-ending hook, now also this feature's orchestration hook) had
actually finished — rewritten to track subscribers directly and await
them via Promise.all, same per-handler error isolation as before. (2)
registerDomainEventHandlers() was only called from server.ts's
production startup path, never from buildApp() — meaning every
integration test in this codebase had zero domain-event subscribers
registered at all. Now called (idempotently) from buildApp() itself,
since domain-event wiring is synchronous application behavior, not a
background-worker concern like the queue.
Adds 8 unit tests (each strategy's pure selection/tie-break logic), a
dedicated round-robin concurrency test verifying no two concurrent
selections collide under real parallel load, and 2 integration test
files covering all five user stories. Full regression (every
pre-existing 002-006 integration test plus every new 007 test) run
together against real Postgres/Redis/MinIO: 124 passed, 9 skipped
(005's AI-key-gated tests, unrelated), 0 failed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Neither Ticket nor Problem carries a skill/problem-type field — the
only real signal is AIDiagnosis.problemType (005), and only for
tickets that went through AI support first. Resolves this before
implementation: use that diagnosis when available, fall back to no
skill constraint (not zero eligible agents) otherwise, matching FR-004's
explicit "rather than failing" and 006's own empty-scope-matches-
everything convention.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
34 tasks across setup, foundational schema work, and five user stories
(automatic resolution, pluggable strategies with a dedicated concurrency
test, durable history, manual assignment, and re-escalation
verification). Notes the T021/T017 sequencing exception where an
implementation dependency crosses story-priority order.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Maps the feature onto the three existing orchestration/{routing,
assignments,orchestration} scaffold stubs. Key decisions: Assignment
refined as a version-row-per-period model (paired with a separate
append-only AssignmentHistory event log), round-robin concurrency
safety via atomic Redis INCR (reusing existing infra, not a new one),
LEAST_LOADED/SKILL_BASED tie-breaks falling back to that same cursor,
currentLoad read but never mutated by this feature, and the escalation
trigger reusing 005's existing domain-event bus rather than a new
notification path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 7 of the roadmap: the orchestration engine (resolving 006's
hierarchy nodes and capability-eligibility lookup against an escalated
ticket), pluggable assignment strategies (ROUND_ROBIN/LEAST_LOADED/
SKILL_BASED/MANUAL/DIRECT) with concurrency-safe round robin, and
durable assignment history. Explicitly excludes SLA/escalation-policy
execution (Phase 8). Triggered via 005's existing domain-event
publishing rather than a new notification path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 6 of the roadmap. Populates the identity/teams and
orchestration/hierarchy module directories (previously empty or
near-empty scaffold stubs) and replaces identity/agents' pre-existing
placeholder, which queried a generic User/UserRole model unrelated to
this system's real architecture (nothing since 002-saas-integration's
CustomerReference has used it).
Teams and agents: active/inactive CRUD, never hard deletion; team
deactivation never cascades to its agents. Skills: compound-unique
upsert on (agentId, skillTag), never duplicates. Availability: a single
current record per agent, deliberately last-write-wins rather than
optimistic-locked — operational telemetry, not a durable business
record. The dynamic hierarchy: a nestable, orderable HierarchyNode tree
with every field (scope, assignment strategy reference, entry/exit
conditions) stored as opaque admin-set data; cycle detection runs only
on reparenting edits (a new node can't form a cycle); every
create/edit/activate/deactivate is audited via AuditLog, reusing
002's existing writer pattern. A capability-eligibility read path
composes hierarchy scope with caller-supplied skills for the future
orchestration/assignment phase (Phase 7) to call, deliberately
excluding availability per doc 05's own capability-before-availability
ordering.
Found and fixed a real bug before it reached tests: the initial
availability upsert reset currentLoad to 0 on every update, not just
creation.
Adds 11 unit tests (cycle detection, capability matching) and 4
integration test files covering all four user stories. Full regression
(every pre-existing 002-005 integration test plus all new ones) run
against real Postgres/Redis/MinIO: 107 passed, 9 skipped (005's
AI-key-gated tests, unrelated), 0 failed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
33 tasks across setup, foundational schema work, and four user stories
(teams/agents, skills/availability, the dynamic hierarchy tree, and a
capability-eligibility read path for the future orchestration phase).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reuses the existing identity/teams, identity/agents, and
orchestration/hierarchy scaffold directories per doc 07's documented
module placement. Key decisions: last-write-wins availability (not
optimistic locking — operational telemetry, not a durable record),
compound-unique skill upsert, cycle detection only on reparenting edits
(not creation, which can't form a cycle), and a capability-eligibility
read path that composes hierarchy scope with caller-supplied skills
while deliberately excluding availability per doc 05's own ordering.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 6 of the roadmap: Team/Agent/AgentSkill/AgentAvailability models,
dynamic HierarchyNode configuration, and a capability-eligibility read
path for the future orchestration phase to call. Supersedes the
pre-existing identity/agents scaffold stub (which queried a generic
User/UserRole model unrelated to this system's real architecture);
leaves identity/customers and identity/auth untouched as out of scope.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real Anthropic Claude integration per explicit product decision: a
ticket's AI session diagnoses the problem via a structured-output call,
applies a DB-configurable confidence-band policy (FR-005), and on
"proceed" reasons and acts through a small permission/risk-gated tool
system (FR-011/FR-012), optionally walking a matching runbook step by
step with the application — never the model — owning the step index
(FR-015/FR-016). Resolution requires real tool evidence, never customer
claims alone (FR-018) — verifyProductResolution is a documented
fail-closed placeholder mirroring the existing malware-scanner precedent,
since no real per-product operational signal exists yet.
AISupportSession.status mirrors onto Ticket.status through 003-ticketing's
existing AI_ANALYZING/AI_TROUBLESHOOTING/AI_VERIFYING/AI_RESOLVED/
HUMAN_ESCALATION state machine, discovered during planning to have been
built anticipating this exact feature. Two circular module dependencies
(escalation<->sessions, tools<->sessions) were designed around rather than
found as bugs: escalation is a pure summary formatter with no state
dependencies of its own, and tools stays a clean leaf module with zero
dependency on ai-support/sessions. Ticket creation enqueues the first
diagnosis turn via the existing queue infrastructure (off the hot path of
the inbound SaaS integration endpoint); a human actor changing ticket
status ends the AI session via the event-bus scaffold that existed in
this codebase but had never been wired to anything.
A real Prisma limitation was found and fixed before it reached tests:
compound-unique upsert rejects null for a nullable key column, so
AIConfidencePolicy uses find-then-update/create instead, same fix class
004 already used for the same underlying limitation.
Adds 9 unit tests (confidence-band, tool-policy-gate, runbook-step-
advance) and 6 integration test files, including the two constitution-
required standing E2E scenarios. AI-independent tests were run against
real Postgres/Redis/MinIO (88 passed, 0 failed across the full suite,
including every pre-existing 002/003/004 test). The AI-dependent tests
compile and skip cleanly via describe.skipIf but were not run against a
live model — no ANTHROPIC_API_KEY was available in this session; a real
key must be supplied before this feature can actually run.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
53 tasks across setup, foundational schema/env/LLM-client work, and five
user stories (diagnosis+confidence policy, clarification loop, gated tool
system, runbook engine, evidence-based verification), plus the two
constitution-required standing E2E scenarios in Polish.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
003-ticketing's ticket-state-machine.ts already defines AI_ANALYZING/
AI_TROUBLESHOOTING/AI_VERIFYING/AI_RESOLVED/HUMAN_ESCALATION ticket
statuses, clearly authored anticipating this feature. Corrects the plan
before implementation: AISupportSession.status now drives Ticket.status
through the existing ticketsService.updateStatus (reusing its optimistic
concurrency), instead of an isolated status field the rest of the system
never sees. Also clarifies that knowledge retrieval is an in-process
service call through knowledge's index.ts, not an HTTP loopback.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two-call reasoning design (structured-output diagnosis, then a separate
knowledge-grounded reasoning/tool call), confidence-band policy as a DB-
configurable gate applied by app code, a deterministic tool-policy gate
that never reads AI free text, an app-owned runbook step index, and a
fail-closed placeholder verification tool mirroring the existing
malware-scanner precedent. Real Anthropic Claude integration per explicit
product decision.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 4 of the roadmap: AI session/diagnosis, confidence-band policy,
permission/risk-gated tool system, runbook execution, and evidence-based
verification. Per explicit decision, reasoning integrates a real LLM
provider (Anthropic Claude) rather than a mock.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements 26 of 28 tasks from specs/004-product-knowledge/tasks.md
across all three user stories -- Phase 3 of the roadmap. First
feature to populate src/modules/ai-support/ (doc 07 places
`knowledge` there; only that submodule is built, matching this
codebase's convention of not pre-building unneeded submodules).
Schema (prisma/schema.prisma + migration):
- KnowledgeEntry, ErrorCode, KnownIssue, Runbook per docs/06,
refining its conceptual flat `version` field into an explicit
version-history mechanism: each edit inserts a new row
(isCurrentVersion flag, compound unique on (code, version) /
(key, productId, version)) instead of overwriting in place -- the
only way "prior versions remain retrievable" (FR-004/FR-009) is
actually true rather than aspirational.
User Story 1 -- knowledge entry authoring/publish/version (P1, MVP):
- draft -> published -> unpublished lifecycle; publish only takes
effect from its effectiveDate.
- Editing uses the same conditional-update-then-insert optimistic
concurrency pattern as 003-ticketing's Ticket.version (409 on a
stale expectedVersion).
- Full version history readable via GET .../versions.
User Story 2 -- error codes, known issues, runbooks (P2):
- ErrorCode + KnownIssue with direct lookup-by-error-code.
- Runbook steps stored as an ordered JSON array, preserved exactly;
same version-on-edit mechanism as knowledge entries; inactive
runbooks are indistinguishable from nonexistent ones on lookup.
User Story 3 -- filtered retrieval (P3):
- GET /knowledge/retrieve: product-scoped, excludes draft/
unpublished/not-yet-effective entries, validated entries ranked
ahead of unvalidated. Deliberately NOT semantic/vector search --
doc 11 gap B1 explicitly defers embedding-model choice to the
future AI-support feature; this is real, usable structured
filtering a semantic layer can sit in front of later.
Found and fixed one real bug before it reached tests: the retrieval
endpoint initially queried by the raw external product id instead of
resolving it to the internal Product.id first (every other endpoint
in this feature does that resolution) -- would have silently
returned zero results for every caller. Fixed with a lenient
tryResolveProductId (empty array, not 404, for an unregistered
product -- matches the "no matches, never an error" contract).
Deliberately skipped (not forgotten, see checklist notes): the two
planned mock-repository unit-test tasks (T004, T019) -- unlike
003-ticketing's state machine, this feature has no pure-logic
surface to isolate from Prisma; coverage comes entirely from
integration tests instead.
All 13 integration test files in the repo (36 tests, spanning this
feature and every prior one) verified passing together against a
real Postgres/Redis/MinIO -- no regressions. Full quality gate
(typecheck/lint/format/architecture/unit tests) passes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/speckit-plan output for 004-product-knowledge: technical context and
constitution gate check (all PASS), Phase 0 research (6 decisions:
new-row-per-version instead of in-place overwrite to satisfy history
retention, conditional-update-then-insert concurrency reusing
003-ticketing's optimistic-locking pattern, structured (non-semantic)
filtered retrieval per doc 11 gap B1, known-issue lookup by error
code, creating the ai-support module group for the first time with
only its knowledge submodule populated, and admin auth consistent
with prior features), Phase 1 data model (KnowledgeEntry/ErrorCode/
KnownIssue/Runbook, refining doc 06's conceptual schema with an
explicit version-history mechanism), the admin CRUD + retrieval
contract, and a 6-scenario quickstart.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/speckit-specify output for 004-product-knowledge (roadmap Phase 3):
3 user stories (authoring/versioning/publishing knowledge entries,
structured error-code/known-issue/runbook records, filtered
retrieval), 14 functional requirements. Full semantic/vector
retrieval is explicitly deferred to the future AI-support feature
(doc 11 gap B1) -- this feature's retrieval is structured filtering,
a real usable contract rather than a placeholder. Quality checklist
passes with no NEEDS CLARIFICATION markers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements all 39 tasks from specs/003-ticketing/tasks.md across all
three user stories -- Phase 5 of the roadmap.
Schema (prisma/schema.prisma + migration):
- Ticket (code, status, version for optimistic concurrency,
idempotencyKey, customerId FK), Problem, TicketMessage,
TicketAttachment per docs/06, with Product/Category/
CustomerReference back-relations.
User Story 1 -- ticket/problem creation (P1, MVP):
- Explicit 12-state lifecycle adjacency table
(ticket-state-machine.ts), not "any transition allowed."
- Ticket code generation (<PRODUCT_CODE>-<YEAR>-<SEQUENCE>) scoped
by the actual code prefix, not productId -- see the collision bug
fixed below.
- Idempotency-key enforcement via atomic create-then-catch-conflict
(never a read-then-write race), completing the FR-012 placeholder
from 002-saas-integration.
- Explicit-reference-only recurring-problem linking (no fuzzy
matching -- that's a future AI-support concern).
- POST /v1/support/requests (002-saas-integration) now creates a
real ticket instead of echoing context back.
- PATCH /tickets/:id/status with expectedVersion-based optimistic
concurrency (409 on stale version, 400 on an invalid transition).
User Story 2 -- typed messages (P2):
- Message type -> visibleToCustomer mapping is a fixed constant map,
never caller-supplied; customer-scoped reads filter at the query
layer so an internal note is never fetched, not just hidden.
- POST/GET /tickets/:id/messages (customer-scoped) and
GET /agent/tickets/:id/messages (agent-scoped).
User Story 3 -- attachment pipeline (P3):
- Presigned-PUT upload (new getPresignedUploadUrl on the existing
storageService) -- file bytes never transit this API.
- A MalwareScanner interface with a fail-closed placeholder
(UnimplementedPlaceholderScanner) since no scanner exists in this
stack -- it always reports 'infected', never silently 'clean'.
- The existing attachments-queue job stub now actually calls the
scanner and updates scanStatus; registerAttachmentWorker() is
wired into bootstrapQueue() (previously defined but never called).
- Downloads are gated on scanStatus === 'clean' -- currently always
refused until a real scanner replaces the placeholder.
- MinIO added to docker-compose.{test,development}.yml for local/CI
S3-compatible storage, matching doc 04's explicit guidance.
Two real bugs found and fixed via integration testing against a
live Postgres/Redis/MinIO (not just typechecked):
- Ticket codes could collide across different products: the
sequence counter was scoped by internal productId, but the code
column's uniqueness is global, and deriveProductCode's 4-character
truncation means different products can share a prefix. Fixed by
counting against the actual code prefix instead.
- Three existing 002-saas-integration integration tests' cleanup
started failing an FK RESTRICT check once ticket creation was
wired in (deleting a Product before the Ticket/Problem that now
reference it). Fixed their afterAll ordering.
All 9 integration test files (24 tests, spanning this feature and
the pre-existing suite) verified passing against real Postgres,
Redis, and MinIO, including a genuine presigned-PUT/GET round trip.
Full quality gate (typecheck/lint/format/architecture/unit tests)
passes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/speckit-tasks output for 003-ticketing: 39 tasks across 6 phases.
MVP scope is Setup+Foundational+US1 (T001-T019) -- every trusted
inbound request producing a real, durable, idempotent,
concurrency-safe ticket, before messages or attachments exist.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/speckit-plan output for 003-ticketing: technical context and
constitution gate check (all PASS), Phase 0 research (9 decisions:
ticket code format, explicit 12-state lifecycle transition table,
optimistic concurrency via version column, idempotency-key upsert
reusing 002's CustomerReference pattern, explicit-reference-only
recurring-problem linking, config-driven message visibility mapping,
presigned-PUT attachment pipeline, a fail-closed placeholder malware
scanner since none exists in this stack, and adding MinIO to Docker
Compose for local/test S3-compatible storage), Phase 1 data model
(Ticket/Problem/TicketMessage/TicketAttachment plus the inbound
request -> ticket creation behavior), the lifecycle/messages/
attachments contract, and a 6-scenario quickstart.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/speckit-specify output for 003-ticketing (roadmap Phase 5): 3 user
stories (immediate ticket/problem creation with idempotency, typed
messages with enforced internal-note privacy, secure attachment
pipeline) and 15 functional requirements. Explicitly scoped to Phase
5 only -- investigation/root-cause/solution/resolution (Phase 9) and
AI diagnosis (Phase 4) are out of scope. Quality checklist passes
with no NEEDS CLARIFICATION markers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements tasks T026-T032 from specs/002-saas-integration/tasks.md
(User Story 3, P3 - the final piece of this feature) plus Polish.
- New bespoke Redis fixed-window counter (checkRateLimit,
src/infrastructure/cache/rate-limiter.ts) rather than
@fastify/rate-limit's default onRequest-stage hook -- that hook
runs before this feature's preHandler-based auth resolves the
integration/user identity the limit needs to key on. A second
preHandler (checkIntegrationRateLimit) runs after
authenticateProductIntegration on the inbound route, checking the
integration-level limit then the per-user limit independently,
each throwing the existing RateLimitError (429
RATE_LIMIT_EXCEEDED) on breach.
- New integration test (inbound-rate-limit.test.ts) verifies both
limits are enforced independently against a real Postgres/Redis:
a throttled user doesn't affect others, and the integration cap
throttles even when no individual user has hit their own limit.
- Docs: contracts/quickstart updated from the placeholder
"RATE_LIMITED" code to the actual reused RATE_LIMIT_EXCEEDED code;
cleaned up a duplicated paragraph in the admin endpoints section;
added a "SaaS Integration" section to README.md documenting the
inbound contract, admin routes (and their known auth-stub
limitation), and how rate limits are configured.
All 32 tasks in tasks.md are now complete -- all three user stories
(P1 trust boundary, P2 admin lifecycle, P3 rate limiting) are
implemented and covered by integration tests verified against a
live database, in addition to unit tests for the crypto/token
primitives. Full quality gate (typecheck/lint/format/architecture/
unit tests) passes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements tasks T021-T025 from specs/002-saas-integration/tasks.md
(User Story 2, P2): an admin can register, rotate, revoke, and change
the status of a ProductIntegration, and retrieve its audit trail.
- ProductIntegrationsService: register (finds-or-creates the Product
by external id), rotate (dual-credential transition window per
research.md), revoke, updateStatus, getAuditTrail -- each writes
its own AuditLog entry via a new shared
integration-audit-log.repository.ts (extracted from the auth
plugin, which now reuses it instead of writing to Prisma directly).
- Routes: POST /admin/products/:externalProductId/integration,
POST/admin/integrations/:id/rotate|revoke, PATCH .../status,
GET .../audit-trail -- gated by the existing fastify.authenticate
(human/admin JWT) decorator.
- New integration test (product-integrations-admin.test.ts) covers
Quickstart Scenarios 5-7 end-to-end against a real Postgres/Redis:
register+rotate+audit-trail, and revoke-takes-effect-immediately.
Verified passing against a live database.
Known, pre-existing limitation flagged (not fixed here, out of
scope): fastify.authenticate is currently a no-op stub with no real
JWT verification, so these admin endpoints aren't actually
access-controlled yet -- that depends on the unimplemented
identity/auth module. Documented in the contract and checklist notes
so it isn't mistaken for done.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements tasks T001-T020 from specs/002-saas-integration/tasks.md
(Setup, Foundational, and User Story 1 - the P1 MVP: every inbound
request is authenticated and trusted before anything happens).
User Story 2 (admin onboarding/rotation/revocation) and User Story 3
(rate limiting) are not yet implemented (T021-T032 remain).
Schema (prisma/schema.prisma + initial migration):
- Replace the placeholder Product model (leftover starter-template
scaffolding: code/description/ProductStatus enum) with the real
docs/06-database-schema.md shape (externalProductId,
supportEnabled, status).
- Add ProductIntegration (credential ref, rotation/revocation state,
allowed scope, per-integration/per-user rate limits) and
CustomerReference models.
- Align AuditLog to docs/06's shape (actor/actorType/entityType/
entityId/reason/metadata) -- the placeholder shape had no fields
to satisfy this feature's audit requirements.
Auth:
- HMAC-signed short-lived tokens (issue/verify) with jti-based replay
defense via Redis and a bounded clock-skew tolerance.
- Credential secrets are AES-256-GCM encrypted at rest (new required
INTEGRATION_CREDENTIAL_ENCRYPTION_KEY env var) since no secret
manager exists in this stack yet -- see research.md "Credential
storage".
- New product-integration-auth.plugin.ts Fastify plugin runs the
validation order in contracts/inbound-request-contract.md and
populates request.reqContext only on full success; every attempt
(success or failure) is audit-logged without ever persisting the
raw token/credential. Unregistered product and invalid credential
return an identical response (FR-010).
- New POST /v1/support/requests endpoint exercises the boundary
end-to-end (ticket creation itself is a future feature).
Also:
- Fix docker-compose.test.yml's container_name collisions --
discovered while testing this change concurrently is now covered
by an app-level regression test (separate commit).
- Fix test:unit to scope to tests/unit only (it was running the
entire tests/** glob including integration tests) -- this feature's
new integration test makes real Prisma/Redis calls, unlike the
prior instantiation-only checks, so the existing glob-scoping gap
became actually harmful.
- Update Jenkinsfile with the new required credential.
Verified: full quality gate (typecheck/lint/format/architecture/
unit tests) passes; all of User Story 1's quickstart scenarios
manually verified end-to-end against a live server + Postgres +
Redis; the new integration test suite verified against a live
database (not run as part of `npm test`, matches existing
test:integration convention).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fastify resolves each encapsulated child context's error handler at
the time that context is registered. app.ts called
app.setErrorHandler()/setNotFoundHandler() AFTER bootstrapRoutes()
had already registered every domain module's routes (each
app.register(someRoutes) call creates its own child context, since
none of the route modules use fastify-plugin). A handler set on the
parent afterwards does not retroactively apply to already-registered
children, so every module's routes were silently falling back to
Fastify's default {statusCode, error, message} error shape instead
of this app's {success:false, error:{code,message,details},
requestId} envelope, for any thrown error -- not specific to any one
feature. Discovered while building and manually verifying the
002-saas-integration feature's inbound endpoint.
Also fixed the generic error-handler branch to preserve a framework
error's own client-facing statusCode (e.g. 400 for malformed JSON)
instead of always reporting 500.
Added a regression test in tests/unit/app.test.ts that fails without
this fix and passes with it (verified both ways).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/speckit-tasks output for 002-saas-integration: 32 tasks across 6
phases. Unlike 001-ci-pipeline, this feature includes test tasks as
first-class (not optional) since it's a security boundary. MVP scope
is Setup+Foundational+US1 (T001-T020) - the inbound trust boundary
alone, before admin lifecycle tooling or rate limiting exist.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/speckit-specify output for 002-saas-integration (roadmap Phase 2):
3 user stories (authenticate/validate inbound requests, admin
onboarding/rotation/revocation, rate limiting) and 12 functional
requirements. Reserves an idempotency-key field on the inbound
contract for the future ticketing feature (docs/11 gap A1) without
implementing dedup here. Quality checklist passes with no
NEEDS CLARIFICATION markers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Production concerns not covered by the original spec (00-10):
idempotency on ticket creation, bi-directional webhook callbacks,
row-level-security tenant isolation, AI prompt-injection defense,
optimistic concurrency on shared mutable state, RAG implementation
specifics, AI cost/token governance, knowledge effectiveness
feedback, CSAT capture, data retention/PII, API versioning/error
contract, localization, and a lower-urgency list. Indexed in
docs/00-INDEX.md as doc 11.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements tasks T001-T016, T018-T022, T024-T026 from
specs/001-ci-pipeline/tasks.md (T017/T023 need a real Jenkins
instance to verify and are left for manual follow-up).
- Add Jenkinsfile: checkout -> install -> environment validation
-> typecheck -> lint (+ architecture check) -> format check ->
unit -> integration -> E2E -> build -> Docker build -> publish
-> deploy, matching the constitution's required stage order.
Secrets are always injected from Jenkins credentials at runtime,
never read from a repo-committed file. Publish/Deploy are skipped
(not failed) on branches with no resolved deploy target.
- Fix docker-compose.test.yml: remove fixed container_name on
app/postgres/redis, which would have made concurrent CI runs
collide (FR-009). Verified locally that two runs under different
-p project names no longer share container/volume/network names.
- Document the pipeline and local .env setup in README.md.
- Mark completed tasks in specs/001-ci-pipeline/tasks.md and record
the container_name/compose-down-env-file findings in the spec's
requirements checklist notes.
Locally verified passing: Dockerfile build, typecheck, lint,
architecture check, format check, unit test suite, and the edited
docker-compose.test.yml bringing up postgres/redis with isolated
per-project container names.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/speckit-tasks output for 001-ci-pipeline: 26 tasks across 5 phases
(Setup, Foundational, US1 validate/build, US2 publish/deploy, Polish),
with the MVP scope being US1 alone (T001-T017).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/speckit-specify + /speckit-plan output for 001-ci-pipeline: feature
spec with 2 user stories and 10 functional requirements, requirements
quality checklist, implementation plan with constitution gate check,
Phase 0 research (6 decisions incl. secrets-from-credentials-store),
Phase 1 data model, pipeline stage contract, and a 5-scenario
quickstart validation guide. No Jenkinsfile yet — that's the
implementation step after /speckit-tasks.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Full system blueprint (docs 01-10): product vision, integration &
security, AI support architecture, ticketing & problem management,
orchestration/SLA/escalation, database schema, backend/frontend
architecture, testing/observability/CI-CD, and the implementation
roadmap. This is the pre-implementation design reference the codebase
is being built against.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>