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
Binary file removed .agents/skills/constructive-access-control.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-agents.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-auth.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-billing.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-blueprints.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-codegen.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-data-modeling.zip
Binary file not shown.
14 changes: 9 additions & 5 deletions .agents/skills/constructive-data-modeling/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,9 +340,12 @@ escape hatch, and combining predicates with expressions.
## Views

Create views via `db.view.create`; `viewType` selects the view body (`View*` node
type). Three optional options control PostgreSQL storage attributes and update
semantics: `securityInvoker` (default `true`), `securityBarrier` (default
`false`), and `checkOption` (`null | 'local' | 'cascaded'`).
type). The body in `data` references its source by **catalog ID**
(`source_table_id` / `field_ids`, etc.), never a raw schema/table name — the server
resolves names and enforces same-database ownership + AST validation. Three optional
options control PostgreSQL storage attributes and update semantics: `securityInvoker`
(default `true`), `securityBarrier` (default `false`), and `checkOption`
(`null | 'local' | 'cascaded'`).

```typescript
await db.view.create({
Expand All @@ -352,7 +355,7 @@ await db.view.create({
name: 'owners_view',
viewType: 'ViewTableProjection',
tableId: ownersTableId,
data: { source_schema: 'app_public', source_table: 'owners' },
data: { source_table_id: ownersTableId },
securityBarrier: true,
checkOption: 'cascaded',
isReadOnly: false,
Expand All @@ -364,7 +367,8 @@ await db.view.create({
// SELECT ... WITH CASCADED CHECK OPTION
```

See [views.md](./references/views.md) for all view options and generated SQL.
See [views.md](./references/views.md) for all view options, the ID-based body, and
ownership/validation guarantees.

## `api_required` (Required API Fields)

Expand Down
38 changes: 36 additions & 2 deletions .agents/skills/constructive-data-modeling/references/views.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ each row compiles to a PostgreSQL `CREATE VIEW`. The view body is chosen by
for the catalog: `ViewTableProjection`, `ViewJoinedTables`, `ViewAggregated`,
`ViewFilteredTable`, `ViewComposite`).

The view body lives in the `data` object and is **referenced by ID, never by raw
schema/table name**. Depending on `viewType`, `data` carries `source_table_id` /
`primary_table_id` / per-join `table_id` (all catalog table UUIDs) and optional
`field_ids`. The server resolves each ID to the physical schema/table name for you
and validates it (see [Referential integrity & ownership](#referential-integrity--ownership)),
so you never hand-write a schema name into a view.

```typescript
await db.view.create({
data: {
Expand All @@ -15,7 +22,9 @@ await db.view.create({
name: 'active_projects',
viewType: 'ViewTableProjection',
tableId: projectsTableId,
data: { source_schema: 'app_public', source_table: 'projects' },
// ID-based body: names are resolved from the catalog server-side.
// field_ids is optional — omit it to SELECT * from the source table.
data: { source_table_id: projectsTableId },
isReadOnly: true,
},
select: { id: true },
Expand Down Expand Up @@ -49,7 +58,7 @@ await db.view.create({
name: 'owners_view',
viewType: 'ViewTableProjection',
tableId: ownersTableId,
data: { source_schema: 'app_public', source_table: 'owners' },
data: { source_table_id: ownersTableId },
securityInvoker: true,
securityBarrier: true,
checkOption: 'cascaded',
Expand Down Expand Up @@ -82,3 +91,28 @@ Set `checkOption: null` to drop the check option again.

> Only `null | 'local' | 'cascaded'` are valid for `checkOption`. A check option
> is meaningful only on updatable views (`isReadOnly: false`).

## Referential integrity & ownership

View bodies reference their source objects by **catalog ID** (`source_table_id`,
`primary_table_id`, per-join `table_id`, `field_ids`) — never by a raw
schema/table name you supply. This is what keeps views safe and tenant-scoped:

- **Ownership is enforced.** Every referenced table ID is checked to belong to the
same `databaseId` as the view; a table from another database is rejected
(generates a `CROSS_DATABASE_REF` error). `field_ids` are likewise scoped to the
view's database. You cannot point a view at another tenant's table by ID or by
guessing its physical schema name.
- **Names are derived, not trusted.** The physical `source_schema` / `source_table`
that end up in the generated `CREATE VIEW` are looked up from the ID server-side,
so a caller can't inject an arbitrary schema name.
- **The query AST is validated.** Before the view is created/altered, the compiled
query is run through the same AST validator used by check constraints, indexes,
and functions — it restricts every referenced schema to the view's own database
schemas (plus framework schemas). This also covers the `ViewComposite` escape
hatch (`data.query_ast`): an out-of-scope schema reference there fails validation
rather than reaching PostgreSQL.

So `db.view.create` gives the same ownership and AST-validation guarantees as the
other declarative DDL surfaces — referencing by ID is the mechanism that provides
them, which is why the name-based shortcut isn't accepted.
Binary file removed .agents/skills/constructive-entities.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-events.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-features.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-flow-graphs.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-frontend.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-hooks.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-i18n.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-jobs.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-notifications.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-orm.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-platform.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-principals.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-realtime.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-search.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-security.zip
Binary file not shown.
Binary file removed .agents/skills/constructive-storage.zip
Binary file not shown.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

# Skill packages are not tracked; regenerate on demand if needed.
*.zip
15 changes: 6 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,16 @@ Skills in this repository are API documentation for app builders. All examples M
SKILL.md # Required: skill definition
scripts/ # Required: executable scripts
{script-name}.sh # Bash scripts (preferred)
{skill-name}.zip # Required: packaged for distribution
```

> `.zip` packages are **not** committed to this repo (they caused constant merge
> conflicts). `*.zip` is gitignored; distribution packaging happens out-of-band.

### Naming Conventions

- **Skill directory**: `kebab-case` (e.g., `constructive-codegen`, `log-monitor`)
- **SKILL.md**: Always uppercase, always this exact filename
- **Scripts**: `kebab-case.sh` (e.g., `deploy.sh`, `fetch-logs.sh`)
- **Zip file**: Must match directory name exactly: `{skill-name}.zip`

### SKILL.md Format

Expand Down Expand Up @@ -152,11 +153,7 @@ This allows agents helping with ORM queries to read only `orm-patterns.md` inste
- Include a cleanup trap for temp files
- Reference the script path as `/mnt/skills/user/{skill-name}/scripts/{script}.sh`

### Creating the Zip Package

After creating or updating a skill:
### Packaging

```bash
cd .agents/skills
zip -r {skill-name}.zip {skill-name}/
```
Skill `.zip` archives are not tracked in this repo — `*.zip` is gitignored. Do not
commit them; if a distributable package is needed it is produced out-of-band.
13 changes: 3 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,6 @@ A collection of skills for AI coding agents working with Constructive tooling. S
| **constructive-hooks** | Generated React Query hooks — query/mutation hooks, cache, optimistic updates |
| **constructive-platform** | Server config, services, domains, deployment, env, cloud functions, cnc CLI |

## Commands

### Package a skill for distribution
```bash
cd .agents/skills
zip -r {skill-name}.zip {skill-name}/
```

## Skill Structure

```
Expand All @@ -51,9 +43,11 @@ zip -r {skill-name}.zip {skill-name}/
SKILL.md # Required: skill definition (keep under 500 lines)
references/ # Optional: detailed documentation
{topic}.md
{skill-name}.zip # Required: packaged for distribution
```

> `.zip` packages are **not** committed to this repo (they caused constant merge
> conflicts). `*.zip` is gitignored; do not commit skill archives.

### SKILL.md Format

Each skill requires a SKILL.md with YAML frontmatter:
Expand Down Expand Up @@ -81,7 +75,6 @@ Optional frontmatter fields:

- Skill directory: `kebab-case`
- SKILL.md: Always uppercase, exact filename
- Zip file: Must match directory name: `{skill-name}.zip`

## Key Design Principles

Expand Down