aravind changes

This commit is contained in:
2026-08-25 16:37:05 +05:30
parent cadea4bd92
commit b6f8655909
27 changed files with 5058 additions and 163 deletions

View File

@@ -4,13 +4,22 @@ The backend for Krow — a Go API over PostgreSQL, with a Python Owliver service
to follow. The Krow frontend lives in a **separate repository** (`krow-demo`)
and is not vendored, copied or modified here.
**Status: Phase 3C — session authentication.** The API serves the endpoints in
`docs/api-contract.md` against PostgreSQL, loaded with the frontend's own demo
dataset. Every endpoint except `GET /health` and the two `/api/v1/auth/*` routes
requires a session: sign in with a password, hold an HttpOnly cookie, and the
server resolves it to a real user on every request. Authorization — which roles
may do what — is Phase 3D and is not implemented. No Owliver, no RAG, no Redis,
NATS or S3.
**Status: authenticated, authorized, multi-tenant.** The API serves the
endpoints in `docs/api-contract.md` against PostgreSQL, loaded with the
frontend's own demo dataset. Every endpoint except `GET /health` and the two
`/api/v1/auth/*` routes requires a session: sign in with a password, hold an
HttpOnly cookie, and the server resolves it to a real user on every request.
Roles are enforced — a deny-by-default policy table answers 403 before any query
runs, and organization scope and talent ownership are SQL predicates, so a row
you may not see answers 404. On top of that sits the agent and skill definition
surface: Markdown with YAML frontmatter, parsed and stored, with full CRUD.
Not built: no Owliver, no RAG, no Redis, no object storage. The agent/skill
runtime is an internal boundary with no executor behind it and no HTTP route
into it.
`docs/KROW_BACKEND_COMPLETE_SUMMARY.md` is the long-form technical account —
architecture, history, and a frank list of the current limitations.
## Layout
@@ -19,27 +28,39 @@ krow-backend/
├── go-api/
│ ├── cmd/
│ │ ├── api/ the HTTP service entrypoint
│ │ └── seed/ loads the demo dataset
│ │ ├── seed/ loads the demo dataset
│ │ └── setpassword/ the only way a password enters the database
│ ├── internal/
│ │ ├── config/ environment loading + validation
│ │ ├── db/ pgx pool and the database health check
│ │ ├── domain/ resource descriptors (generated from the schema)
│ │ ├── domain/ resource descriptors (generated) + the policy table
│ │ ├── repo/ pgx access layer — every statement built here
│ │ ├── service/ validation, org scoping, contract semantics
│ │ ├── auth/ argon2id passwords, sessions, the user store
│ │ ├── authctx/ the authenticated identity a request carries
│ │ ├── orgctx/ the organization a request runs as
│ │ ├── definition/ the agent/skill Markdown + YAML frontmatter parser
│ │ ├── runtime/ agent/skill load, validate, resolve, dispatch
│ │ ├── httpserver/ router, handlers, /health, graceful shutdown
│ │ ├── seeder/ fixture loading + the attendanceSeed.js port
│ │ └── testutil/ disposable migrated + seeded test database
│ └── go.mod
├── migrations/ SQL migrations — the source of truth for the schema
├── seed/fixtures/ seed.json, generated from the frontend repository
├── docs/api-contract.md the frozen client contract
├── docs/
│ ├── api-contract.md the frozen client contract
│ └── KROW_BACKEND_COMPLETE_SUMMARY.md the long-form technical account
├── infrastructure/ deployment definitions (empty until later)
├── scripts/ verify_schema.sql, gen_resources.py
├── scripts/ verify_schema.sql, gen_resources.py, oracle.mjs
├── Makefile developer, migration and seed tasks
└── .env.example
```
`migrations/`, `seed/`, `scripts/` and `infrastructure/` sit beside `go-api/`
rather than inside it because none of them is Go: the migrations are run by the
`golang-migrate` CLI, the generator is Python, the parser oracle is Node, and a
second service (Owliver, Python) is expected to consume the same schema.
## Architecture
```
@@ -82,10 +103,16 @@ make run # http://127.0.0.1:8080/health
## The API
38 endpoints, specified in `docs/api-contract.md`. Every one exists because a
frontend call site exists — a table in the database is never a reason for an
endpoint. `DELETE /job-postings/{id}` and `POST /shift-records` return 405
because nothing in the frontend deletes a posting or creates a shift record.
54 registered routes: 34 entity routes across 14 resources, 10 agent and skill
definition routes, 4 `/me` routes, 2 `/auth` routes, 2 workflow routes, the
Owliver suggestion route and `GET /health`. The entity routes and the suggestion
route are specified in `docs/api-contract.md` (§2 and §2A); the definition and
workflow routes are not yet in the contract and are documented in the source and
its tests. Every
entity route exists because a frontend call site exists — a table in the
database is never a reason for an endpoint. `DELETE /job-postings/{id}` and
`POST /shift-records` return 405 because nothing in the frontend deletes a
posting or creates a shift record.
```bash
curl localhost:8080/api/v1/job-postings
@@ -110,8 +137,19 @@ Set a password before signing in: the seeded user's `password_hash` is NULL unti
curl -b jar localhost:8080/api/v1/me
curl -b jar -X POST localhost:8080/api/v1/auth/logout
Authorization is **not** implemented. `users.role` is carried on the identity and
consulted nowhere: any signed-in user reaches every endpoint. That is Phase 3D.
**Authorization is enforced.** `users.role` — `admin`, `employer` or `talent`,
never the self-editable `account_type` — is the only authority. Entity routes
are gated by the deny-by-default policy table in `internal/domain/policy.go`:
a resource with no policy permits nothing to anyone, and `Server.authorize`
answers 403 before any query runs. Row visibility is a SQL predicate rather than
a filter — the organization scope always, plus an ownership clause for talent
callers — so a row outside it is never fetched and answers 404, which does not
distinguish "exists but not yours" from "does not exist".
The ten definition routes are the exception: they are not `domain.Resource`
values, so the descriptor machinery does not reach them and their role checks
are written inline in `internal/service/definitions.go`. Tenancy and ownership
*are* enforced for them, in the repository predicates.
## Seeding
@@ -135,16 +173,30 @@ records created through the API survive a re-seed.
make test # go test ./...
```
55 tests. The database-backed ones build a disposable database — dropped,
recreated, migrated and seeded per run, named `krow_backend_autotest_<pid>` so
concurrent test packages cannot collide. They skip rather than fail when
PostgreSQL is unreachable.
175 test functions. The database-backed ones build a disposable database —
dropped, recreated, migrated and seeded per run, named
`krow_backend_autotest_<pid>` so concurrent test packages cannot collide. They
skip rather than fail when PostgreSQL is unreachable.
Seed assertions compare the database against the fixture field-by-field rather
than against numbers typed into a test. The two ordering guarantees
(`NULLS LAST` in both directions, and the `, id` tiebreaker) are covered by tests
verified to fail when the guarantee is removed.
**Parser conformance.** The Go definition parser must agree with the frontend's
JavaScript one. `internal/definition/conformance_test.go` asserts against
`internal/definition/testdata/oracle.json`, which is not written by hand: it is
captured by running the real frontend module graph through Vite, so the fixture
records what the JS parser actually does rather than what anyone believes it
does. Regenerating it needs a `krow-demo` checkout, and is not part of
`make test`:
```bash
node scripts/oracle.mjs go-api/internal/definition/testdata/oracle.json
```
`scripts/cases.mjs` holds the adversarial corpus that file is captured over.
`GET /health` is public and says only whether traffic should be sent here:
`{"status": "ok"}` with `200`, `{"status": "degraded"}` with `200` when the
database is up but unmigrated or left dirty, and `{"status": "unavailable"}`