Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type Document @wes_table @wes_tenant(by: "org_id") @wes_rls(enabled: true) {
- [Key Features](#key-features)
- [Advanced Features](#advanced-features)
- [FAQ](#faq)
- [Evidence, HOLMES, and Observability](#evidence-holmes-and-observability)

---

Expand Down Expand Up @@ -200,6 +201,40 @@ Wesley is engineered for safety, speed, and confidence.

---

## Evidence, HOLMES, and Observability

- EvidenceMap is the authoritative mapping between schema elements and generated artifacts. Generators record evidence under stable UIDs:
- Tables: `tbl:TableName`
- Columns: `col:TableName.columnName`
- SQL comments like `COMMENT ON COLUMN … IS 'uid: …'` are human-readable hints. Tooling (SourceMap, scoring, HOLMES) reads from EvidenceMap, not from the comment strings.
- To surface a SQL error in the original GraphQL SDL, load the evidence bundle (`.wesley/bundle.json`) and call `findSourceForSql(evidenceMap, { file, line })`.
- Scores (SCS/TCI/MRI) are computed from EvidenceMap; ensure generators record artifacts to keep scores accurate.

Example: map a SQL error back to SDL

```js
import fs from 'node:fs/promises';
import { EvidenceMap } from '@wesley/core';
// Temporary deep import until re-exported at package root
import { findSourceForSql } from '@wesley/core/src/application/SourceMap.mjs';

// Load the bundle written by `wesley generate --emit-bundle`
const raw = await fs.readFile('.wesley/bundle.json', 'utf8');
const bundle = JSON.parse(raw);

// Evidence payload may be nested; normalize it
const payload = bundle?.evidence?.evidence ? bundle.evidence : bundle;
const ev = EvidenceMap.fromJSON(payload);

// If a failure mentions out/schema.sql:123
const mapped = findSourceForSql(ev, { file: 'out/schema.sql', line: 123 });
if (mapped?.source) {
console.log(`SDL: ${mapped.source.file} lines ${mapped.source.lines} (uid ${mapped.uid})`);
}
```

---

## Comparison

| | Hand-written | ORMs | **Wesley** |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export class PostgreSQLGenerator {
for (const field of table.getFields()) {
if (!field.isVirtual()) {
const idf = (await import('../Identifier.mjs')).identifier;
const fieldUid = field.directives?.['@uid'] || `col_${idf.toTableSQLName(table.name)}_${idf.toSQL(field.name)}`;
const fieldUid = field.directives?.['@uid'] || `col:${table.name}.${field.name}`;
statements.push(`COMMENT ON COLUMN "${sqlTable}"."${idf.toSQL(field.name)}" IS 'uid: ${fieldUid}';`);
}
}
Expand Down
30 changes: 30 additions & 0 deletions packages/wesley-core/src/domain/security/SECURITY_STRATEGY.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,36 @@ Choosing the right policy clause:

These templates match RLSPresets output (owner/tenant/public-read, etc.).

### 5. **Evidence and Source Mapping**

- EvidenceMap is the source of truth for where schema elements land in generated artifacts. Generators record evidence using stable UIDs:
- Tables: `tbl:TableName`
- Columns: `col:TableName.columnName`
- SQL `COMMENT ON ... 'uid: …'` strings are informational hints only. Consumers (SourceMap, scoring, HOLMES) read from EvidenceMap.
- To map a SQL error back to SDL, use SourceMap utilities (e.g., `findSourceForSql(evidenceMap, { file, line })`).

Example (Node):

```js
import fs from 'node:fs/promises';
import { EvidenceMap } from '@wesley/core';
// Temporary deep import until re-exported at package root
import { findSourceForSql } from '@wesley/core/src/application/SourceMap.mjs';

// Load bundle produced by: wesley generate --emit-bundle
const raw = await fs.readFile('.wesley/bundle.json', 'utf8');
const bundle = JSON.parse(raw);

// Accept either flat { evidence: {...} } or nested { evidence: { evidence: {...} } }
const payload = bundle?.evidence?.evidence ? bundle.evidence : bundle;
const ev = EvidenceMap.fromJSON(payload);

// Suppose a failure references out/schema.sql:123
const result = findSourceForSql(ev, { file: 'out/schema.sql', line: 123 });
// result: { uid: 'col:Table.field', source: { file: 'schema.graphql', lines: 'X-Y', columns: 'A-B' } }
```



## 🧪 Security Testing

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ CREATE TABLE IF NOT EXISTS "data_types" (
PRIMARY KEY ("id")
);
COMMENT ON TABLE "data_types" IS 'uid: tbl:DataTypes';
COMMENT ON COLUMN "data_types"."id" IS 'uid: col_data_types_id';
COMMENT ON COLUMN "data_types"."text_field" IS 'uid: col_data_types_text_field';
COMMENT ON COLUMN "data_types"."int_field" IS 'uid: col_data_types_int_field';
COMMENT ON COLUMN "data_types"."big_int_field" IS 'uid: col_data_types_big_int_field';
COMMENT ON COLUMN "data_types"."float_field" IS 'uid: col_data_types_float_field';
COMMENT ON COLUMN "data_types"."decimal_field" IS 'uid: col_data_types_decimal_field';
COMMENT ON COLUMN "data_types"."bool_field" IS 'uid: col_data_types_bool_field';
COMMENT ON COLUMN "data_types"."date_field" IS 'uid: col_data_types_date_field';
COMMENT ON COLUMN "data_types"."time_field" IS 'uid: col_data_types_time_field';
COMMENT ON COLUMN "data_types"."datetime_field" IS 'uid: col_data_types_datetime_field';
COMMENT ON COLUMN "data_types"."json_field" IS 'uid: col_data_types_json_field';
COMMENT ON COLUMN "data_types"."uuid_field" IS 'uid: col_data_types_uuid_field';
COMMENT ON COLUMN "data_types"."inet_field" IS 'uid: col_data_types_inet_field';
COMMENT ON COLUMN "data_types"."id" IS 'uid: col:DataTypes.id';
COMMENT ON COLUMN "data_types"."text_field" IS 'uid: col:DataTypes.textField';
COMMENT ON COLUMN "data_types"."int_field" IS 'uid: col:DataTypes.intField';
COMMENT ON COLUMN "data_types"."big_int_field" IS 'uid: col:DataTypes.bigIntField';
COMMENT ON COLUMN "data_types"."float_field" IS 'uid: col:DataTypes.floatField';
COMMENT ON COLUMN "data_types"."decimal_field" IS 'uid: col:DataTypes.decimalField';
COMMENT ON COLUMN "data_types"."bool_field" IS 'uid: col:DataTypes.boolField';
COMMENT ON COLUMN "data_types"."date_field" IS 'uid: col:DataTypes.dateField';
COMMENT ON COLUMN "data_types"."time_field" IS 'uid: col:DataTypes.timeField';
COMMENT ON COLUMN "data_types"."datetime_field" IS 'uid: col:DataTypes.datetimeField';
COMMENT ON COLUMN "data_types"."json_field" IS 'uid: col:DataTypes.jsonField';
COMMENT ON COLUMN "data_types"."uuid_field" IS 'uid: col:DataTypes.uuidField';
COMMENT ON COLUMN "data_types"."inet_field" IS 'uid: col:DataTypes.inetField';
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ CREATE TABLE IF NOT EXISTS "products" (
PRIMARY KEY ("id")
);
COMMENT ON TABLE "products" IS 'uid: tbl:Product';
COMMENT ON COLUMN "products"."id" IS 'uid: col_products_id';
COMMENT ON COLUMN "products"."name" IS 'uid: col_products_name';
COMMENT ON COLUMN "products"."tags" IS 'uid: col_products_tags';
COMMENT ON COLUMN "products"."categories" IS 'uid: col_products_categories';
COMMENT ON COLUMN "products"."metadata" IS 'uid: col_products_metadata';
COMMENT ON COLUMN "products"."prices" IS 'uid: col_products_prices';
COMMENT ON COLUMN "products"."id" IS 'uid: col:Product.id';
COMMENT ON COLUMN "products"."name" IS 'uid: col:Product.name';
COMMENT ON COLUMN "products"."tags" IS 'uid: col:Product.tags';
COMMENT ON COLUMN "products"."categories" IS 'uid: col:Product.categories';
COMMENT ON COLUMN "products"."metadata" IS 'uid: col:Product.metadata';
COMMENT ON COLUMN "products"."prices" IS 'uid: col:Product.prices';
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ CREATE TABLE IF NOT EXISTS "users" (
UNIQUE ("email")
);
COMMENT ON TABLE "users" IS 'uid: tbl:User';
COMMENT ON COLUMN "users"."id" IS 'uid: col_users_id';
COMMENT ON COLUMN "users"."email" IS 'uid: col_users_email';
COMMENT ON COLUMN "users"."name" IS 'uid: col_users_name';
COMMENT ON COLUMN "users"."age" IS 'uid: col_users_age';
COMMENT ON COLUMN "users"."created_at" IS 'uid: col_users_created_at';
COMMENT ON COLUMN "users"."id" IS 'uid: col:User.id';
COMMENT ON COLUMN "users"."email" IS 'uid: col:User.email';
COMMENT ON COLUMN "users"."name" IS 'uid: col:User.name';
COMMENT ON COLUMN "users"."age" IS 'uid: col:User.age';
COMMENT ON COLUMN "users"."created_at" IS 'uid: col:User.createdAt';
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ CREATE TABLE IF NOT EXISTS "tenants" (
UNIQUE ("name")
);
COMMENT ON TABLE "tenants" IS 'uid: tbl:Tenant';
COMMENT ON COLUMN "tenants"."id" IS 'uid: col_tenants_id';
COMMENT ON COLUMN "tenants"."name" IS 'uid: col_tenants_name';
COMMENT ON COLUMN "tenants"."plan" IS 'uid: col_tenants_plan';
COMMENT ON COLUMN "tenants"."id" IS 'uid: col:Tenant.id';
COMMENT ON COLUMN "tenants"."name" IS 'uid: col:Tenant.name';
COMMENT ON COLUMN "tenants"."plan" IS 'uid: col:Tenant.plan';
CREATE TABLE IF NOT EXISTS "users" (
"id" uuid NOT NULL,
"email" text NOT NULL,
Expand All @@ -19,10 +19,10 @@ CREATE TABLE IF NOT EXISTS "users" (
FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE NO ACTION
);
COMMENT ON TABLE "users" IS 'uid: tbl:User';
COMMENT ON COLUMN "users"."id" IS 'uid: col_users_id';
COMMENT ON COLUMN "users"."email" IS 'uid: col_users_email';
COMMENT ON COLUMN "users"."tenant_id" IS 'uid: col_users_tenant_id';
COMMENT ON COLUMN "users"."role" IS 'uid: col_users_role';
COMMENT ON COLUMN "users"."id" IS 'uid: col:User.id';
COMMENT ON COLUMN "users"."email" IS 'uid: col:User.email';
COMMENT ON COLUMN "users"."tenant_id" IS 'uid: col:User.tenantId';
COMMENT ON COLUMN "users"."role" IS 'uid: col:User.role';
CREATE INDEX IF NOT EXISTS "users_tenant_id_idx" ON "users" ("tenant_id");
COMMENT ON INDEX "users_tenant_id_idx" IS 'uid: idx_users_tenant_id';
CREATE TABLE IF NOT EXISTS "projects" (
Expand All @@ -36,11 +36,11 @@ CREATE TABLE IF NOT EXISTS "projects" (
FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE NO ACTION
);
COMMENT ON TABLE "projects" IS 'uid: tbl:Project';
COMMENT ON COLUMN "projects"."id" IS 'uid: col_projects_id';
COMMENT ON COLUMN "projects"."name" IS 'uid: col_projects_name';
COMMENT ON COLUMN "projects"."tenant_id" IS 'uid: col_projects_tenant_id';
COMMENT ON COLUMN "projects"."owner_id" IS 'uid: col_projects_owner_id';
COMMENT ON COLUMN "projects"."settings" IS 'uid: col_projects_settings';
COMMENT ON COLUMN "projects"."id" IS 'uid: col:Project.id';
COMMENT ON COLUMN "projects"."name" IS 'uid: col:Project.name';
COMMENT ON COLUMN "projects"."tenant_id" IS 'uid: col:Project.tenantId';
COMMENT ON COLUMN "projects"."owner_id" IS 'uid: col:Project.ownerId';
COMMENT ON COLUMN "projects"."settings" IS 'uid: col:Project.settings';
CREATE INDEX IF NOT EXISTS "projects_tenant_id_idx" ON "projects" ("tenant_id");
COMMENT ON INDEX "projects_tenant_id_idx" IS 'uid: idx_projects_tenant_id';
CREATE INDEX IF NOT EXISTS "projects_owner_id_idx" ON "projects" ("owner_id");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ CREATE TABLE IF NOT EXISTS "events" (
UNIQUE ("slug")
);
COMMENT ON TABLE "events" IS 'uid: tbl:Event';
COMMENT ON COLUMN "events"."id" IS 'uid: col_events_id';
COMMENT ON COLUMN "events"."name" IS 'uid: col_events_name';
COMMENT ON COLUMN "events"."slug" IS 'uid: col_events_slug';
COMMENT ON COLUMN "events"."start_date" IS 'uid: col_events_start_date';
COMMENT ON COLUMN "events"."end_date" IS 'uid: col_events_end_date';
COMMENT ON COLUMN "events"."status" IS 'uid: col_events_status';
COMMENT ON COLUMN "events"."category_id" IS 'uid: col_events_category_id';
COMMENT ON COLUMN "events"."organizer_id" IS 'uid: col_events_organizer_id';
COMMENT ON COLUMN "events"."id" IS 'uid: col:Event.id';
COMMENT ON COLUMN "events"."name" IS 'uid: col:Event.name';
COMMENT ON COLUMN "events"."slug" IS 'uid: col:Event.slug';
COMMENT ON COLUMN "events"."start_date" IS 'uid: col:Event.startDate';
COMMENT ON COLUMN "events"."end_date" IS 'uid: col:Event.endDate';
COMMENT ON COLUMN "events"."status" IS 'uid: col:Event.status';
COMMENT ON COLUMN "events"."category_id" IS 'uid: col:Event.categoryId';
COMMENT ON COLUMN "events"."organizer_id" IS 'uid: col:Event.organizerId';
CREATE INDEX IF NOT EXISTS "events_name_idx" ON "events" ("name");
COMMENT ON INDEX "events_name_idx" IS 'uid: idx_events_name';
-- Skipped redundant index: Index on slug is covered by unique constraint on slug
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ CREATE TABLE IF NOT EXISTS "users" (
UNIQUE ("email")
);
COMMENT ON TABLE "users" IS 'uid: tbl:User';
COMMENT ON COLUMN "users"."id" IS 'uid: col_users_id';
COMMENT ON COLUMN "users"."email" IS 'uid: col_users_email';
COMMENT ON COLUMN "users"."id" IS 'uid: col:User.id';
COMMENT ON COLUMN "users"."email" IS 'uid: col:User.email';
CREATE TABLE IF NOT EXISTS "posts" (
"id" uuid NOT NULL,
"title" text NOT NULL,
Expand All @@ -17,11 +17,11 @@ CREATE TABLE IF NOT EXISTS "posts" (
FOREIGN KEY ("author_id") REFERENCES "users"("id") ON DELETE NO ACTION
);
COMMENT ON TABLE "posts" IS 'uid: tbl:Post';
COMMENT ON COLUMN "posts"."id" IS 'uid: col_posts_id';
COMMENT ON COLUMN "posts"."title" IS 'uid: col_posts_title';
COMMENT ON COLUMN "posts"."content" IS 'uid: col_posts_content';
COMMENT ON COLUMN "posts"."author_id" IS 'uid: col_posts_author_id';
COMMENT ON COLUMN "posts"."published_at" IS 'uid: col_posts_published_at';
COMMENT ON COLUMN "posts"."id" IS 'uid: col:Post.id';
COMMENT ON COLUMN "posts"."title" IS 'uid: col:Post.title';
COMMENT ON COLUMN "posts"."content" IS 'uid: col:Post.content';
COMMENT ON COLUMN "posts"."author_id" IS 'uid: col:Post.authorId';
COMMENT ON COLUMN "posts"."published_at" IS 'uid: col:Post.publishedAt';
CREATE TABLE IF NOT EXISTS "comments" (
"id" uuid NOT NULL,
"content" text NOT NULL,
Expand All @@ -32,7 +32,7 @@ CREATE TABLE IF NOT EXISTS "comments" (
FOREIGN KEY ("author_id") REFERENCES "users"("id") ON DELETE NO ACTION
);
COMMENT ON TABLE "comments" IS 'uid: tbl:Comment';
COMMENT ON COLUMN "comments"."id" IS 'uid: col_comments_id';
COMMENT ON COLUMN "comments"."content" IS 'uid: col_comments_content';
COMMENT ON COLUMN "comments"."post_id" IS 'uid: col_comments_post_id';
COMMENT ON COLUMN "comments"."author_id" IS 'uid: col_comments_author_id';
COMMENT ON COLUMN "comments"."id" IS 'uid: col:Comment.id';
COMMENT ON COLUMN "comments"."content" IS 'uid: col:Comment.content';
COMMENT ON COLUMN "comments"."post_id" IS 'uid: col:Comment.postId';
COMMENT ON COLUMN "comments"."author_id" IS 'uid: col:Comment.authorId';
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ CREATE TABLE IF NOT EXISTS "organizations" (
PRIMARY KEY ("id")
);
COMMENT ON TABLE "organizations" IS 'uid: tbl:Organization';
COMMENT ON COLUMN "organizations"."id" IS 'uid: col_organizations_id';
COMMENT ON COLUMN "organizations"."name" IS 'uid: col_organizations_name';
COMMENT ON COLUMN "organizations"."owner_id" IS 'uid: col_organizations_owner_id';
COMMENT ON COLUMN "organizations"."id" IS 'uid: col:Organization.id';
COMMENT ON COLUMN "organizations"."name" IS 'uid: col:Organization.name';
COMMENT ON COLUMN "organizations"."owner_id" IS 'uid: col:Organization.ownerId';
CREATE TABLE IF NOT EXISTS "documents" (
"id" uuid NOT NULL,
"title" text NOT NULL,
Expand All @@ -18,8 +18,8 @@ CREATE TABLE IF NOT EXISTS "documents" (
FOREIGN KEY ("org_id") REFERENCES "organizations"("id") ON DELETE NO ACTION
);
COMMENT ON TABLE "documents" IS 'uid: tbl:Document';
COMMENT ON COLUMN "documents"."id" IS 'uid: col_documents_id';
COMMENT ON COLUMN "documents"."title" IS 'uid: col_documents_title';
COMMENT ON COLUMN "documents"."content" IS 'uid: col_documents_content';
COMMENT ON COLUMN "documents"."org_id" IS 'uid: col_documents_org_id';
COMMENT ON COLUMN "documents"."created_by" IS 'uid: col_documents_created_by';
COMMENT ON COLUMN "documents"."id" IS 'uid: col:Document.id';
COMMENT ON COLUMN "documents"."title" IS 'uid: col:Document.title';
COMMENT ON COLUMN "documents"."content" IS 'uid: col:Document.content';
COMMENT ON COLUMN "documents"."org_id" IS 'uid: col:Document.orgId';
COMMENT ON COLUMN "documents"."created_by" IS 'uid: col:Document.createdBy';
Loading