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
50 changes: 50 additions & 0 deletions .changeset/metadata-brief-teaches-the-lookup-factory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
---

Agent-facing documentation only — this PR releases nothing to HotCRM users, so the
frontmatter above is deliberately empty (the sanctioned "releases nothing"
declaration that `.github/workflows/changeset-check.yml` documents, on par with the
`skip-changeset` label). The package is `private: true` with no `files[]`, and every
changed line lives in `AGENTS.md` or `.github/instructions/metadata.md`. Nothing
under `src/` moves: no object, field, view, dataset or exported symbol changed, and
the source token ratchet is byte-identical.

`AGENTS.md` told authors to write `reference_to`, and `.github/instructions/metadata.md`
— the brief the Data Modeler reads before writing an `*.object.ts` — faithfully copied
it. `FieldSchema` has no such key. Measured against the pinned `@objectstack/spec`
17.3.0, the engine answers:

Unrecognized key(s) on this field: `reference_to`. Did you mean `reference_to`
-> `reference`? Until this shape was closed these were dropped silently — the
field was still created, minus whatever the key was meant to constrain, protect
or compute.

That tail is the reason this was worth a card rather than a spelling fix: before the
shape was closed, an author following the brief got a field that was created but
silently stripped of the constraint they wrote. The prescription and its downstream
copy are corrected together, because correcting the brief alone would have put it in
conflict with the file it is derived from.

The brief also taught a definition shape the repo does not have. TypeScript itself
is the witness for the import half:

error TS2305: Module '"@objectstack/spec"' has no exported member 'ObjectSchema'.

The root exports neither a value nor a type by that name, so the taught
`as ObjectSchema` cast could never have compiled — and a cast checks nothing even
when it does. The corrected example imports from `@objectstack/spec/data`, builds with
the parsing factory `ObjectSchema.create(...)`, and exports a named `const` that
`src/objects/index.ts` re-exports for auto-registration. All 18 `src/objects/*.object.ts`
files already do exactly this, and the example now type-checks and parses end to end.

Two same-class defects in the same brief are fixed here and flagged for the seat in the
PR body rather than folded in silently. `enable.search` is not a key either
(`searchable` is), and it became load-bearing only because this PR swaps a cast that
checked nothing for a factory that parses. Field example 4's `formula:` / `return_type:`
are both rejected, and following the engine's own fix-up hint to `returnType: 'currency'`
still fails, since that enum is `number|text|boolean|date`.

One same-class instance is deliberately NOT fixed here, because it is outside this
card's declared file surface, and is reported instead: `.github/tasks/autonomous_feature_dev.md`
still instructs the agent to check that "all foreign keys (`reference_to`) point to
valid objects".
35 changes: 18 additions & 17 deletions .github/instructions/metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ Your primary output is `*.object.ts` files.
You must adhere to the `ServiceObject` interface:

```typescript
import { ObjectSchema } from '@objectstack/spec';
import { ObjectSchema, Field } from '@objectstack/spec/data';

export default {
export const Contract = ObjectSchema.create({
name: 'crm_contract', // snake_case, Singular, crm_ prefixed. DB Table Name.
label: 'Contract', // Human readable label.
pluralLabel: 'Contracts',
Expand All @@ -21,15 +21,15 @@ export default {
enable: {
trackHistory: true, // Audit field changes
apiEnabled: true, // Expose to REST/GraphQL
search: true, // Index in global search
searchable: true, // Index in global search
activities: true, // Allow tasks/events attachment
},

// Fields Dictionary
fields: {
// ... fields here
}
} as ObjectSchema;
},
});
```

### Constraint Checklist
Expand All @@ -39,6 +39,10 @@ export default {
name at runtime, in the DB and in the REST URL. The FILE stays unprefixed
(`src/objects/contract.object.ts` declares `name: 'crm_contract'`).
- **Label**: User-friendly Title Case.
- **Export**: a NAMED `const` built by the `ObjectSchema.create(...)` factory, which PARSES the
definition. `src/objects/index.ts` re-exports that name for auto-registration, so a default
export is never picked up. An `as ObjectSchema` cast is not a substitute: a cast is a type
position for a value and checks nothing at all.

## 2. Field Definitions

Expand Down Expand Up @@ -75,21 +79,18 @@ status: {
defaultValue: 'draft'
},

// 3. Relationship (Lookup)
account: {
type: 'lookup',
// 3. Relationship (Lookup) — the target is the prefixed object name,
// declared in account.object.ts
account: Field.lookup('crm_account', {
label: 'Account',
reference_to: 'crm_account', // The prefixed object name, declared in account.object.ts
required: true
},
required: true,
}),

// 4. Formula
total_amount: {
type: 'formula',
// 4. Formula — `F` is the CEL template tag, imported from '@objectstack/spec'
total_amount: Field.formula({
label: 'Total',
formula: 'quantity * unit_price',
return_type: 'currency'
}
expression: F`record.quantity * record.unit_price`,
}),
```

## 3. Validation Rules
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ hotcrm/
- strictly typed using `@objectstack/spec`.
- **NEVER** use YAML or JSON for metadata.
- Object names must be `snake_case`.
- **All HotCRM business object names MUST use the `crm_` prefix and the prefix MUST be written explicitly in source** (e.g., `crm_account`, `crm_opportunity`, `crm_knowledge_article`). **No automatic prefix injection by the runtime.** Open architectural decision: AI-authored metadata is fragile around "context-aware" naming, so we trade verbosity for grep-ability. Cross-references via `reference_to` / `lookup` / `masterDetail` MUST use the prefixed name. Cube `sql:` fields, view `data.object`, hook `object:`, action `objectName:`, navigation `objectName:`, dashboard `object:`, translation `objects.{key}`, REST URLs, and DB table names ALL use the same prefixed name. The name in source = the name at runtime = the name in DB = the name in URL = the name in docs. No translation layer.
- **All HotCRM business object names MUST use the `crm_` prefix and the prefix MUST be written explicitly in source** (e.g., `crm_account`, `crm_opportunity`, `crm_knowledge_article`). **No automatic prefix injection by the runtime.** Open architectural decision: AI-authored metadata is fragile around "context-aware" naming, so we trade verbosity for grep-ability. Lookup and master-detail targets (`Field.lookup(...)` / `Field.masterDetail(...)`) MUST use the prefixed name. Cube `sql:` fields, view `data.object`, hook `object:`, action `objectName:`, navigation `objectName:`, dashboard `object:`, translation `objects.{key}`, REST URLs, and DB table names ALL use the same prefixed name. The name in source = the name at runtime = the name in DB = the name in URL = the name in docs. No translation layer.

2. **ObjectQL (No-SQL)**:
- Data access MUST use **ObjectQL**, reached through the `ctx.api` surface. There is
Expand Down Expand Up @@ -138,7 +138,7 @@ When asked to implement a feature, you MUST follow this **Thinking Process**:
### Phase 3: Self-Correction
After generating code, ask yourself:
* [ ] Did I respect the strictly typed `ServiceObject` interface?
* [ ] Are all `reference_to` pointing to real objects?
* [ ] Do all lookup / master-detail targets name real objects?
* [ ] Did I use ObjectQL instead of SQL?
* [ ] are file names strictly `snake_case`?

Expand Down Expand Up @@ -345,7 +345,7 @@ in order:

## ⚠️ Constraint Checklist

- **Object Naming**: All HotCRM business objects MUST be prefixed with `crm_` (e.g. `crm_account`, `crm_opportunity`). The roster is deliberately **not** restated here — `src/objects/*.object.ts` is its source of truth. *Supersedes the hand-maintained fifteen-name list that stood in this bullet, which had already drifted three objects behind the tree — 2026-08-31 ruling, item 5.* All references — `reference_to`, `lookup`, `masterDetail`, cube `sql`, view `data.object`, hook `object`, navigation `objectName`, action `objectName`, dashboard `object` — MUST use the prefixed form. Platform objects keep their existing `sys_*` prefix.
- **Object Naming**: All HotCRM business objects MUST be prefixed with `crm_` (e.g. `crm_account`, `crm_opportunity`). The roster is deliberately **not** restated here — `src/objects/*.object.ts` is its source of truth. *Supersedes the hand-maintained fifteen-name list that stood in this bullet, which had already drifted three objects behind the tree — 2026-08-31 ruling, item 5.* All references — `Field.lookup(...)` / `Field.masterDetail(...)` targets, cube `sql`, view `data.object`, hook `object`, navigation `objectName`, action `objectName`, dashboard `object` — MUST use the prefixed form. Platform objects keep their existing `sys_*` prefix.
- **i18n**: Every new object must have entries in all 4 locale files (`src/translations/{en,zh-CN,es-ES,ja-JP}.ts`) — label, pluralLabel, all field labels + option labels, view labels, navigation labels. No new feature ships without all 4 locales.
- **Docs**: Every new object/feature requires user-facing documentation under `content/docs/` (e.g. `getting-started/`, `guides/`, `marketing/`, `analytics/`, `administration/`) written for business users + admins (not developers) — business concepts, never a hand-copied machine roster (see **Documentation discipline** below).
- **Documentation**: written in English, then translated — `content/docs` ships `.zh-Hans.mdx` and `.zh-Hant.mdx` pages beside the English ones, and that Chinese surface follows the three rules under **Documentation discipline** below. *Supersedes "All documentation MUST be in English", a blanket the shipped tree already contradicted — 2026-08-31 ruling, item 6.*
Expand Down
Loading