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
16 changes: 16 additions & 0 deletions .changeset/upgrade-objectstack-v17-rc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'hotcrm': minor
---

Upgrade HotCRM to ObjectStack 17.0.0-rc.1. The app now declares protocol 17
compatibility, preserves existing required-field database constraints explicitly,
uses the live metadata contracts for skills, flows, and APIs, and enables the
date buckets that make CRM analytics aggregate by month, day, and quarter.

Also completes the 17.0 permission migration that the version bump alone leaves
half-done: `allowExport` became an opt-in bit whose absence DENIES, so every
CSV/XLSX list export and every report export is authored explicitly on the
profiles that need it. Approval nodes drop their hand-rolled org-owner backstop
for the native `onEmptyApprovers` policy, media fields declare the `accept` and
`maxSize` constraints 17.0 enforces server-side, and the platform-upgrade
checklist now covers the `os migrate` data gates.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ Everything merged after the 2.2.2 tag. Not yet versioned or published.

### Changed

- **ObjectStack platform → 17.0.0-rc.1** across all `@objectstack/*` packages (from 16.1.0). The manifest protocol handshake and marketplace `specVersion` now declare the 17.0.0-rc.1 compatibility line; API methods, skills and flow notifications use only live metadata keys; pre-17 required fields preserve their database `NOT NULL` constraints explicitly; and the analytics semantic layer now declares the month/day/quarter buckets v17 executes correctly.
- **`allowExport` is authored on every profile that needs it (17.0 opt-in export axis, upstream #3544).** Before 17.0 an unset `allowExport` inherited read, so "can list ⇒ can export". 17.0 inverted the default: `resolveUserExportAllowed` demands an explicit `allowExport: true` and neither `viewAllRecords` nor `modifyAllRecords` substitutes, so an unset bit now DENIES — at both bulk-egress doors, the list views' built-in `exportOptions` and `ReportService.assertExportAllowed`. No profile carried the bit, which would have 403'd every CSV/XLSX and report export for every user, `system_admin` included, while `os validate`, `os build` and the whole test suite stayed green. The five objects with a real export surface (`crm_account`, `crm_case`, `crm_contact`, `crm_lead`, `crm_opportunity`) now carry the grant on each profile that already reads them; `guest_portal` deliberately carries none (ADR-0090 D9 forbids binding a high-privilege set to the `guest` anchor). Pinned by `test/authorization-coverage.test.ts`, which fails both on an export surface nobody can reach and on a grant with no surface behind it.
- Approval nodes in `opportunity_approval` use the native `onEmptyApprovers: 'admin_rescue'` policy instead of the hand-rolled `org_membership_level: 'owner'` approver. That entry existed only to avoid the empty-position dead-end, and with `behavior: 'first_response'` it overshot — it made an org owner a routine approver on every deal over $100K, not a rescue when the bench is empty.
- Media fields (`crm_product.image`, `crm_product.datasheet`, `crm_account.logo`, `crm_contact.avatar`) declare `accept` and `maxSize`. 17.0 enforces both server-side against the stored `sys_file` (ADR-0104 D3 wave 2); before, the upload widget read them but `FieldSchema` dropped them at parse, so the constraint never reached a direct API caller.
- Docs: [`docs/MAINTENANCE.md`](docs/MAINTENANCE.md) §3.2 covers the `os migrate` data gates (`files-to-references`, `value-shapes`) and the `OS_ALLOW_LAX_*` escape hatches. `pnpm verify` and `demo:reset` cannot catch these — they start from an empty database, so a green local run says nothing about an in-place upgrade.
- **ObjectStack platform → 16.1.0 stable** across all `@objectstack/*` packages (from the 16.0.0-rc.1 line pinned in 2.2.0). [#465](https://github.com/objectstack-ai/hotcrm/pull/465)
- CI: bump `actions/checkout` 4 → 7 ([#422](https://github.com/objectstack-ai/hotcrm/pull/422)) and `actions/setup-node` 4 → 6 ([#421](https://github.com/objectstack-ai/hotcrm/pull/421)).
- Docs: README hero states HotCRM's size in tokens ([#483](https://github.com/objectstack-ai/hotcrm/pull/483)); drifted README counts fixed ([#466](https://github.com/objectstack-ai/hotcrm/pull/466)).
Expand Down
4 changes: 1 addition & 3 deletions content/docs/customization/ai-skills.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ export const RenewalPitchSkill = defineSkill({
description: 'Drafts renewal talk tracks from account and contract context.',
instructions: `When asked for a renewal pitch, inspect the current
account and contract records, summarize risk, and draft a concise
customer-facing talk track.`,
customer-facing talk track.`,
tools: ['describe_object', 'query_records'],
triggerPhrases: ['draft renewal pitch', 'renewal talk track'],
permissions: ['crm:account:read'],
});
```

Expand Down
4 changes: 1 addition & 3 deletions content/docs/customization/ai-skills.zh-Hans.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ export const RenewalPitchSkill = defineSkill({
description: 'Drafts renewal talk tracks from account and contract context.',
instructions: `When asked for a renewal pitch, inspect the current
account and contract records, summarize risk, and draft a concise
customer-facing talk track.`,
customer-facing talk track.`,
tools: ['describe_object', 'query_records'],
triggerPhrases: ['draft renewal pitch', 'renewal talk track'],
permissions: ['crm:account:read'],
});
```

Expand Down
4 changes: 1 addition & 3 deletions content/docs/customization/ai-skills.zh-Hant.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ export const RenewalPitchSkill = defineSkill({
description: 'Drafts renewal talk tracks from account and contract context.',
instructions: `When asked for a renewal pitch, inspect the current
account and contract records, summarize risk, and draft a concise
customer-facing talk track.`,
customer-facing talk track.`,
tools: ['describe_object', 'query_records'],
triggerPhrases: ['draft renewal pitch', 'renewal talk track'],
permissions: ['crm:account:read'],
});
```

Expand Down
41 changes: 41 additions & 0 deletions docs/MAINTENANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,47 @@ can silently invalidate existing metadata or **seed data** (see §4). Treat ever
native binary was built for a different Node ABI — `pnpm rebuild better-sqlite3`
and restart. This is an environment issue, not an app change.
7. Note the new platform version in `CHANGELOG.md`.
8. **Check the release notes for `os migrate` steps that run against DATA, not
metadata** — see §3.2. `pnpm verify` cannot catch these: they gate runtime
behaviour on a deployment flag, so a fresh install is clean and an in-place
upgrade is not.

### 3.2 Data migrations and enforcement gates (`os migrate`)

Steps 1–7 cover the app's own metadata. A major can additionally ship
migrations that rewrite or re-validate **stored rows**, gated behind a
deployment flag so the new enforcement turns on only once the data is known
clean. `demo:reset` hides these entirely — it starts from an empty database, so
a green local run says nothing about an existing deployment.

Run the metadata replay first, then each data gate as a dry run before
`--apply`:

```bash
os migrate meta --from <previous major> # replays renames and key conversions
os migrate files-to-references # dry run: media fields → sys_file records
os migrate files-to-references --apply # convert, verify, record the flag
os migrate value-shapes # dry run: scan reference & JSON validity
os migrate value-shapes --apply # record the gate if the scan is clean
```

For **17.0** specifically: `files-to-references` backfills the four media
fields this app declares (`crm_product.image`, `crm_product.datasheet`,
`crm_account.logo`, `crm_contact.avatar`) into `sys_file` records, and
`value-shapes` scans reference and JSON columns. Neither is needed for a fresh
install — no seed data populates a media field — but an in-place upgrade needs
both before strict validation is safe to enable.

If a scan reports rows it cannot convert, the escape hatches downgrade the new
enforcement to warnings while you fix the data. They are temporary, not a
destination:

| Variable | Effect |
| --- | --- |
| `OS_ALLOW_LAX_MEDIA_VALUES=1` | File-value verification warns instead of failing |
| `OS_ALLOW_LAX_VALUE_SHAPES=1` | Reference/JSON validation warns instead of failing |
| `OS_ALLOW_LAX_ACTION_PARAMS=1` | Action-param shape enforcement warns instead of failing |
| `OS_DATA_VALUE_SHAPE_STRICT_ENABLED=1` | Opt into strict value shapes immediately, without the gate |

### 3.1 Destructive schema drift — database-only columns after an upgrade

Expand Down
2 changes: 1 addition & 1 deletion docs/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pnpm verify
| --- | --- |
| Node.js | `>=22` |
| pnpm | `>=10.0.0` |
| ObjectStack packages | `16.1.0` |
| ObjectStack packages | `17.0.0-rc.1` |
| Local dev port | `4001` |

## Current Metadata Inventory
Expand Down
8 changes: 6 additions & 2 deletions docs/developers/code_examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,6 @@ export const WarrantySummarySkill = defineSkill({
summarize status, identify expired or soon-expiring warranties, and
recommend the next action.`,
tools: ['describe_object', 'query_records'],
triggerPhrases: ['summarize warranties', 'warranty risk'],
});
```

Expand All @@ -222,7 +221,7 @@ sources resolve, and only two:
`query_records`, `query_data`, `get_record`, `aggregate_data`,
`search_knowledge` and `visualize_data`. From 17.0 the authoritative set is
exported as `PLATFORM_PROVIDED_TOOL_NAMES` from `@objectstack/spec/system`;
on 16.1.0 it is transcribed into `test/skills-integrity.test.ts`. Note
before 17.0 it is transcribed into `test/skills-integrity.test.ts`. Note
`search_knowledge` retrieves over a *declared knowledge source*, and there is
currently nowhere in a skills-only app to declare one.
- **`action_<name>`** — materialised from an Action that opts in with
Expand All @@ -239,6 +238,11 @@ Note there is no `permissions` key on a skill — `SkillSchema` has no such fiel
so one is silently stripped ([#511](https://github.com/objectstack-ai/hotcrm/pull/511)).
Gate access on the Actions the skill calls instead.

Likewise, `triggerPhrases` was removed in 17.0 because the runtime never used
it. Describe a skill's intended requests in its `description` and
`instructions`, and use `triggerConditions` only when routing needs an
explicit context predicate.

## Common Registration Points

| New metadata | Add file under | Export from |
Expand Down
3 changes: 1 addition & 2 deletions objectstack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default defineStack({
// `objectstack migrate meta` replay) instead of failing deep in a schema
// parse. Bump together with `specVersion` on every platform upgrade
// (docs/MAINTENANCE.md §3).
engines: { protocol: '^16.0.0' },
engines: { protocol: '^17.0.0-rc.1' },
},

// ─── Platform capabilities this app needs ─────────────────────────
Expand Down Expand Up @@ -106,7 +106,6 @@ export default defineStack({
defaultLocale: 'en',
supportedLocales: ['en', 'zh-CN', 'ja-JP', 'es-ES'],
fallbackLocale: 'en',
fileOrganization: 'per_locale',
},

translations: Object.values(translations),
Expand Down
4 changes: 2 additions & 2 deletions objectstack.manifest.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"$schema": "https://schemas.objectstack.dev/template-manifest.json",
"name": "hotcrm",
"specVersion": "^16.0.0-rc.1",
"specVersion": "^17.0.0-rc.1",
"engines": {
"protocol": "^16.0.0"
"protocol": "^17.0.0-rc.1"
},
"manifestId": "app.objectstack.hotcrm",
"displayName": "HotCRM",
Expand Down
Loading
Loading