Skip to content

Add AI workflow automation and predictive analytics protocols - #83

Merged
hotlong merged 3 commits into
mainfrom
copilot/add-ai-workflow-automation
Jan 23, 2026
Merged

hotlong merged 3 commits into
mainfrom
copilot/add-ai-workflow-automation

Conversation

Copilot AI commented Jan 23, 2026 •

Copy link
Copy Markdown
Contributor

Adds two AI protocol specifications to enable intelligent automation and ML-powered decision making in ObjectStack applications.

AI Workflow Automation (ai/workflow-automation.zod.ts)

Core capabilities:

  • Triggers: record_created, record_updated, field_changed, scheduled, manual, webhook, batch
  • AI Tasks: classify, extract, summarize, generate, predict, translate, sentiment, entity_recognition, anomaly_detection, recommendation
  • Execution: Sequential/parallel modes, batch processing, conditional execution, retry logic
  • Post-processing: Field updates, notifications, flow triggers, webhooks

Example usage:

const workflow: AIWorkflowAutomation = {
  name: 'auto_triage_tickets',
  objectName: 'support_ticket',
  trigger: 'record_created',
  aiTasks: [
    {
      type: 'classify',
      inputFields: ['description'],
      outputField: 'category',
      classes: ['bug', 'feature', 'question']
    },
    {
      type: 'sentiment',
      inputFields: ['description'],
      outputField: 'urgency_score'
    }
  ],
  postActions: [{
    type: 'field_update',
    name: 'route_to_team',
    config: { /* ... */ }
  }]
}

Predictive Analytics (ai/predictive.zod.ts)

Model types: classification, regression, clustering, forecasting, anomaly_detection, recommendation, ranking

Features:

  • Feature engineering with 8 transformation types (normalize, one-hot encode, log transform, etc.)
  • Hyperparameters for tree-based, neural network, clustering, time-series algorithms
  • Training configuration: cross-validation, early stopping, GPU support
  • Evaluation metrics: accuracy/precision/recall/F1/AUC, MSE/RMSE/MAE/R², silhouette score, MAPE
  • Auto-retraining with drift detection
  • Model explainability

Example usage:

const model: PredictiveModel = {
  name: 'lead_conversion_predictor',
  type: 'classification',
  objectName: 'lead',
  target: 'converted',
  features: [
    { name: 'engagement_score', field: 'engagement_score', dataType: 'numeric', transformation: 'standardize' },
    { name: 'company_size', field: 'employee_count', dataType: 'numeric', transformation: 'log_transform' },
    { name: 'industry', field: 'industry', dataType: 'categorical', transformation: 'one_hot_encode' }
  ],
  hyperparameters: { numTrees: 200, maxDepth: 8, learningRate: 0.1 },
  autoRetrain: true,
  enableExplainability: true
}

Technical notes

  • All schemas follow Zod-first design with runtime validation and TypeScript inference
  • 75 test cases cover real-world scenarios (support automation, lead scoring, document processing, forecasting)
  • Generated 18 JSON schemas and 18 MDX documentation files
  • Resolved naming collision with existing license Feature schema by using ModelFeature
  • No breaking changes: all 1401 existing tests pass
Original prompt

管理
⚠️ 需要增强的部分

  1. AI 工作流自动化 (优先级: 中等)

// 建议新增: src/ai/workflow-automation.zod.ts
export const AIWorkflowSchema = z.object({
trigger: z.enum(['record_created', 'field_changed', 'scheduled']),
aiTasks: z.array(z.object({
type: z.enum(['classify', 'extract', 'summarize', 'generate', 'predict']),
model: z.string(),
inputFields: z.array(z.string()),
outputField: z.string(),
})),
});
影响: 智能业务流程自动化

  1. 预测分析协议 (优先级: 低)

// 建议新增: src/ai/predictive.zod.ts
export const PredictiveModelSchema = z.object({
type: z.enum(['classification', 'regression', 'clustering', 'forecasting']),
features: z.array(z.string()),
target: z.string(),
hyperparameters: z.record(z.any()).optional(),
});
影响: 数据驱动决策


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@vercel

vercel Bot commented Jan 23, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
spec Ready Ready Preview, Comment Jan 23, 2026 8:44am

Request Review

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…alytics

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Add AI workflow automation schema to project Add AI workflow automation and predictive analytics protocols Jan 23, 2026
Copilot AI requested a review from hotlong January 23, 2026 08:43
@hotlong
hotlong marked this pull request as ready for review January 23, 2026 09:27
Copilot AI review requested due to automatic review settings January 23, 2026 09:27
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests protocol:ai labels Jan 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR is very large. Consider breaking it into smaller PRs for easier review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces AI workflow automation and predictive analytics protocol schemas to the ObjectStack spec, with matching JSON Schemas, documentation, and tests. It extends the AI surface area to support intelligent workflows (triggers, tasks, post-actions) and ML model definitions (features, training, evaluation, drift, prediction IO).

Changes:

  • Added AIWorkflowAutomation protocol schemas, batch/execution result types, re-exports, and comprehensive scenario-based tests.
  • Added PredictiveModel / predictive analytics schemas (features, hyperparameters, training, metrics, prediction request/response, drift), plus exhaustive tests.
  • Generated and wired corresponding JSON Schema definitions and MDX reference documentation for all new AI protocol components.

Reviewed changes

Copilot reviewed 41 out of 41 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/spec/src/ai/workflow-automation.zod.ts Defines Zod-first schemas and inferred types for AI workflow triggers, tasks, schedules, post-actions, workflow definition, batch execution, and execution results.
packages/spec/src/ai/workflow-automation.test.ts Adds extensive Vitest coverage for workflow automation schemas, including edge cases and real-world workflows.
packages/spec/src/ai/predictive.zod.ts Defines Zod schemas and inferred types for predictive model types, features, hyperparameters, training config, evaluation metrics, model definition, prediction IO, and drift detection.
packages/spec/src/ai/predictive.test.ts Provides broad test coverage for predictive schemas across model types and realistic ML use cases.
packages/spec/src/ai/index.ts Re-exports new workflow automation and predictive analytics schemas from the AI module entrypoint.
packages/spec/json-schema/WorkflowSchedule.json JSON Schema for WorkflowSchedule, mirroring the schedule Zod schema used by AI workflows.
packages/spec/json-schema/WorkflowFieldCondition.json JSON Schema for WorkflowFieldCondition, used to constrain field_changed workflow triggers.
packages/spec/json-schema/TrainingConfig.json JSON Schema for TrainingConfig, representing training data splits, strategy, and resource limits.
packages/spec/json-schema/PredictiveModelType.json JSON Schema enum for predictive model types (classification, regression, etc.).
packages/spec/json-schema/PredictiveModel.json JSON Schema for PredictiveModel, aligning with the predictive model Zod schema.
packages/spec/json-schema/PredictionResult.json JSON Schema for PredictionResult; currently treats prediction as optional, unlike the Zod schema (see comment).
packages/spec/json-schema/PredictionRequest.json JSON Schema for PredictionRequest, describing model name, record IDs, direct input data, and return flags.
packages/spec/json-schema/PostProcessingAction.json JSON Schema for PostProcessingAction, used in AI workflow post-action definitions.
packages/spec/json-schema/ModelFeature.json JSON Schema for ModelFeature, capturing feature identity, source, type, and transformation.
packages/spec/json-schema/ModelDrift.json JSON Schema for ModelDrift, mirroring drift detection configuration/result schema.
packages/spec/json-schema/Hyperparameters.json JSON Schema for Hyperparameters, covering general, tree-based, NN, clustering, and time-series params.
packages/spec/json-schema/EvaluationMetrics.json JSON Schema for EvaluationMetrics, including classification, regression, clustering, and time-series metrics.
packages/spec/json-schema/BatchAIWorkflowExecution.json JSON Schema for BatchAIWorkflowExecution, specifying workflow name, record IDs, and batch options.
packages/spec/json-schema/AIWorkflowTrigger.json JSON Schema enum for AIWorkflowTrigger values.
packages/spec/json-schema/AIWorkflowExecutionResult.json JSON Schema for AIWorkflowExecutionResult, describing workflow run outcome and per-task results.
packages/spec/json-schema/AIWorkflowAutomation.json JSON Schema for AIWorkflowAutomation, matching the workflow automation Zod schema.
packages/spec/json-schema/AITaskType.json JSON Schema enum for AITaskType values.
packages/spec/json-schema/AITask.json JSON Schema for AITask, reflecting the AI task Zod schema structure.
content/docs/references/ai/WorkflowSchedule.mdx MDX reference doc for WorkflowSchedule, detailing schedule fields used by AI workflows.
content/docs/references/ai/WorkflowFieldCondition.mdx MDX reference doc for WorkflowFieldCondition, describing field-based trigger conditions.
content/docs/references/ai/TrainingConfig.mdx MDX reference doc for TrainingConfig, documenting training strategy and split fields.
content/docs/references/ai/PredictiveModelType.mdx MDX listing the allowed PredictiveModelType enum values.
content/docs/references/ai/PredictiveModel.mdx MDX reference for PredictiveModel, summarizing all model configuration properties.
content/docs/references/ai/PredictionResult.mdx MDX doc for PredictionResult; currently documents prediction as optional, diverging from the Zod schema.
content/docs/references/ai/PredictionRequest.mdx MDX reference for PredictionRequest, documenting request fields and semantics.
content/docs/references/ai/PostProcessingAction.mdx MDX reference for PostProcessingAction, describing action types and configuration.
content/docs/references/ai/ModelFeature.mdx MDX reference for ModelFeature, documenting feature identity, type, and transformation.
content/docs/references/ai/ModelDrift.mdx MDX reference for ModelDrift, explaining drift types, severity, and metrics.
content/docs/references/ai/Hyperparameters.mdx MDX reference for Hyperparameters, covering standard ML hyperparameter fields.
content/docs/references/ai/EvaluationMetrics.mdx MDX reference for EvaluationMetrics, listing supported metric fields.
content/docs/references/ai/BatchAIWorkflowExecution.mdx MDX reference for BatchAIWorkflowExecution, documenting batch execution options.
content/docs/references/ai/AIWorkflowTrigger.mdx MDX listing allowed AIWorkflowTrigger values.
content/docs/references/ai/AIWorkflowExecutionResult.mdx MDX reference for AIWorkflowExecutionResult, summarizing workflow run result properties.
content/docs/references/ai/AIWorkflowAutomation.mdx MDX reference for AIWorkflowAutomation, documenting workflow identity, triggers, tasks, and options.
content/docs/references/ai/AITaskType.mdx MDX listing allowed AITaskType values.
content/docs/references/ai/AITask.mdx MDX reference for AITask, describing task configuration fields.
content/docs/references/ai/WorkflowSchedule.mdx MDX reference for WorkflowSchedule, used by AIWorkflow docs.
content/docs/references/ai/WorkflowFieldCondition.mdx MDX reference for workflow field conditions (used in AI workflows).

Comment on lines +247 to +252
export const PredictionResultSchema = z.object({
modelName: z.string(),
modelVersion: z.string(),
recordId: z.string().optional(),
prediction: z.any().describe('The predicted value'),
confidence: z.number().optional().describe('Confidence score (0-1)'),

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PredictionResultSchema currently requires prediction (z.any()) at the Zod level, but the generated JSON Schema (PredictionResult.json) and MDX docs (PredictionResult.mdx) treat prediction as optional (not listed in required and documented without the required checkmark). This discrepancy means CLI/runtime validation and JSON-schema–driven tooling/IDE hints will disagree on whether prediction must be present. Consider aligning these by either making prediction optional in the Zod schema or marking it as required in the JSON schema/docs, depending on the intended contract for prediction results.

Copilot uses AI. Check for mistakes.
@hotlong
hotlong merged commit c636886 into main Jan 23, 2026
17 checks passed
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…psMap rows, derived from the renderers' read points (objectui#8348 Q1-C) (objectstack-ai#18403)

Fixes objectstack-ai#18305

Executes the objectui#8348 ruling 「8348 以协议为准」 (decision batch objectstack-ai#83,
2026-09-08) and batch objectstack-ai#136 item 3 (Q1-C, maintainer 「同意」):
`ComponentPropsMap` gains `object-map`, `object-gantt` and
`object-tree`, each row's key set derived the **objectstack-ai#7751 way — from the
objectui renderer's own read points**, at the `.objectui-sha` pin
`53ded82b`.

Not derived from `@object-ui/types`' mirror. That mirror standing in as
the authority for map and gantt is the defect this card closes, and for
`object-tree` the mirror is measurably wrong about four keys (below).

## The objectstack-ai#7751 derivation table, extended by three rows

Every citation is a line in the objectui checkout at `53ded82b`. The
shared record-source ladder is `resolveRecordSourceConfig` in
`core/src/utils/record-source.ts` — rung 1 `data` (`:151`, returned
VERBATIM as a `ViewData`), rung 2 `staticData` (`:155`, wrapped into `{
provider: 'value' }`), rung 3 `objectName` (`:162`).

### `object-map` — `plugin-map/src/ObjectMap.tsx`, registry shell
`plugin-map/src/index.tsx`

| key | read point | declared as |
|---|---|---|
| `objectName` | ladder rung 3; `:793` (fetch dep), `:890` (navigation)
| `z.string().optional()` |
| `data` | `:169` array-shorthand head, then `resolveRecordSourceConfig`
at `:174` | `ViewDataSchema` |
| `staticData` | ladder rung 2 | `z.array(z.unknown())` |
| `filter` | `:742` — `$filter: schema.filter` |
`z.array(ViewFilterRuleSchema)` |
| `sort` | `:743` — `$orderby: convertSortToQueryParams(schema.sort)` |
`z.array(SortItemSchema)` |
| `map` | `:370` — `getMapConfig` branch 1, the author face; the
registration's declared `{ name: 'map', type: 'object' }` input |
`z.unknown()` (see note) |
| `mapStyle` | `:365` — `schema.mapStyle \|\| schema.map?.style` |
`z.string()` |
| `navigation` | `:889` — `useNavigationOverlay` | `z.unknown()` |
| `enableClustering` | `:905` — `enableClustering ??
(schema.enableClustering \|\| …)` | `z.boolean()` |

Measured and deliberately NOT declared: the flat `map`-config spellings
`getMapConfig` branch 2 reads at `:382-396` (`locationField`,
`latitudeField`, `longitudeField`, `titleField`, `descriptionField`,
`zoom`, `center`) — the ObjectView/ListView flatten product, ruled an
internal transport form, not a second authoring surface (maintainer
ruling objectui#5018, 2026-08-17); `style`, which `:283` reads ONLY to
say it is not consumed as a map style (objectui#5017) and which is the
node's own inline CSS record; and the React props of `ObjectMapProps`
(`clusterRadius`, the `data` ARRAY prop, the four callbacks,
`className`, `dataSource`).

### `object-gantt` — `plugin-gantt/src/ObjectGantt.tsx`, registry shell
`plugin-gantt/src/index.tsx`

| key | read point | declared as |
|---|---|---|
| `objectName` | ladder rung 3; `:1359` (layout key), `:1491`
(navigation), `:1873` (export name) | `z.string().optional()` |
| `data` | `resolveRecordSourceConfig` at `:593` | `ViewDataSchema` |
| `staticData` | ladder rung 2 | `z.array(z.unknown())` |
| `filter` | `:738` — `$filter: schema.filter` |
`z.array(ViewFilterRuleSchema)` |
| `sort` | `:739` — `$orderby: convertSortToQueryParams(schema.sort)` |
`z.array(SortItemSchema)` |
| `gantt` | `:499-501` — `getGanttConfig` branch 1; the registration's
`{ name: 'gantt', type: 'object' }` input, and `:501` validates it
against this repo's OWN `GanttConfigSchema` | `GanttConfigSchema` |
| `navigation` | `:1487` — `schema.navigation ?? { mode: 'drawer' }` |
`z.unknown()` |
| `label` | `:1871` — `resolveInlineI18nLabel(schema.label,
displayLocale)` in the export-file-name chain (`:1849` is the comment
ABOVE that chain, not a read) | `I18nLabelSchema` |
| `skipWeekends` | `:1205` — `!!sw` into the working calendar |
`z.boolean()` |
| `holidays` | `:1206` — `new Set(hol)` for the duration math |
`z.array(z.string())` |
| `persistLayout` | `:1357` — `schema.persistLayout === false` |
`z.boolean()` |
| `viewName` | `:1359` — the `objectName:viewName` storage key |
`z.string()` |
| `markers` | `:1826` — `markers={schema.markers}` |
`z.array(z.unknown())` |
| `criticalPath` | `:1829` —
`criticalPathDefault={!!schema.criticalPath}` | `z.boolean()` |
| `showBaselines` | `:1832` — `schema.showBaselines !== false` |
`z.boolean()` |
| `readOnly` | `:1833`, `:1916` — `!!schema.readOnly` | `z.boolean()` |
| `mobileReadOnly` | `:1834` — `schema.mobileReadOnly !== false` |
`z.boolean()` |

Measured and deliberately NOT declared: the 30 flat `GanttConfig`
spellings branch 2 reads at `:513-543` (objectui#6469 inherited the
objectui#5018 ruling for this block); `title`, which this renderer never
reads; and a row cap — the reload's `$top` is the platform ceiling
`NON_GRID_ROW_CEILING_TOP`, which the renderer's own comment marks 「⛔
Not authorable」.

### `object-tree` — `plugin-tree/src/ObjectTree.tsx`, registry shell
`plugin-tree/src/index.tsx`

| key | read point | declared as |
|---|---|---|
| `objectName` | ladder rung 3; `:534`, `:570`, `:605` |
`z.string().optional()` |
| `data` | `resolveRecordSourceConfig` at `:359` — rung 1, returned
VERBATIM as a `ViewData`. That ONE site is the whole support for this
arm | `ViewDataSchema` |
| `staticData` | ladder rung 2 | `z.array(z.unknown())` |
| `filter` | `:474` — `$filter: schema.filter` |
`z.array(ViewFilterRuleSchema)` |
| `tree` | `:108` — the nested block `getTreeConfig` reads; the
registration's `{ name: 'tree', type: 'object' }` input |
`TreeConfigSchema` |
| `navigation` | `:591` — `useNavigationOverlay({ navigation: (schema as
any).navigation })` | `z.unknown()` |

**`data` IS on the row, and that is the measurement, not family
symmetry.** The card left this conditional — 「if the renderer's authored
surface has no `data`, the row declares none」 — and the condition is
FALSE here: the renderer reaches `schema.data` through the shared ladder
at `:359`, which returns it verbatim as a `ViewData`. ⛔ `:496` is
**not** a second site for that arm, and an earlier revision of this body
over-claimed it as one: `(rest as any).data ?? (schema as any).data` is
gated by `Array.isArray(passed)` on the very next line, so it honours
only the bare-ARRAY shorthand this row **refuses** — the same shorthand
`object-map` measures and declines above. One ladder site is sufficient
and `:359` is it, so the row stands on a corrected citation. What is
absent is the DECLARATION, on both published faces: `ObjectTreeSchema`
on `@object-ui/types` at this pin declares no `data`, no `staticData`,
no `filter` and no `navigation` at all, and makes `objectName` required
— four keys the renderer reads. That is precisely the mirror-is-wrong
shape the ruling puts the protocol in front of, so the row follows the
read points and the mirror is the face that has to follow.

**No `sort`.** `ObjectTree.tsx:473-484` issues `$filter`, `$top` and
`$expand` and no `$orderby`, and nothing else in the file reads an
order. A `sort` door here would publish a key with no read site — the
exact defect objectstack-ai#7751 exists to remove, in the other direction.

## Three derivation decisions, each pinned rather than left to review

1. **The flat config spellings stay unauthorable on all three.**
`ObjectView` / `ListView` build these nodes by spreading `options.map` /
`options.gantt` / `options.tree`'s CONTENTS at the top level, carrying
no block key at all; that is an internal transport form (objectui#5018,
inherited by objectui#6469), and the map and gantt renderers name every
flat key beside a present block as IGNORED in a dev warning. Writing one
now gets a wrong-layer prescription naming the config block — the
channel `object-calendar` already uses. Each set is held equal to the
config block it points at (`ListMapConfigSchema.shape`;
`GanttConfigSchema.shape` plus the legacy singular `dependencyField`;
`TreeConfigSchema.shape` plus `titleField`, which `getTreeConfig:117`
reads only as `labelField`'s last fallback), so it cannot drift
silently.
2. **`filter` and `sort` are the family's one orthography from birth** —
`ViewFilterRule[]` (ui#6206-B reaching the family: objectstack-ai#15449, batch objectstack-ai#55
option A) and `SortItem[]` (objectui#8221, batch objectstack-ai#77 option B). A new
door on a family-wide ruling has no "measured before the ruling" arm.
The whole-map census pins in `component.test.ts` already demanded it:
every `filter` door must refuse the record form, and only
`record:related_list` may take a string `sort`.
3. **Config-block VALUES follow the read point.** `gantt` takes
`GanttConfigSchema` because `ObjectGantt.tsx:501` literally validates
the authored block against that schema, imported from
`@objectstack/spec/ui`. `tree` takes `TreeConfigSchema`, which objectstack-ai#15469
shut against unknown keys on this very measurement, re-measured
unchanged here. `map` stays `z.unknown()` — and that is measured, not
conservative-by-default: the spec's own `ListMapConfigSchema` is strict
and declares no `style`, the key `getMapConfig:365` reads at
`schema.map?.style`, so pointing this door at it would refuse a value
the renderer honours today. The pin records the reason so the later
value ratchet has one.

## Measured file face

13 files, all additive to a published surface; nothing retired, nothing
narrowed.

```
.changeset/18305-component-props-map-map-gantt-tree.md      new
packages/spec/src/ui/component.zod.ts                       +3 schemas, +3 rows, +3 guidance sets
packages/spec/src/ui/component.test.ts                      +1 describe, 4 door lists extended
packages/spec/src/ui/filter-rule-array-guidance.test.ts     +3 doors (seven -> ten)
packages/spec/src/ui/component-type-vocabulary.test.ts      +1 pin
packages/spec/authorable-surface/ui.json                    +32 rows   (generated)
packages/spec/json-schema.manifest/ui.json                  +3 entries (generated)
packages/spec/api-surface/ui.json                           +9 exports (generated)
packages/spec/export-origins/ui.json                        generated
packages/spec/declaration-map/ui.json                       generated
content/docs/references/ui/component.mdx                    generated
content/docs/references/index.mdx                           generated
docs/audits/2026-07-unknown-key-strictness-ledger.counts.md generated (444 -> 447 sites)
```

No corpus moves: a whole-tree grep for an authored `object-map` /
`object-gantt` / `object-tree` page node finds zero outside
`sdui.manifest.json` (a registry dump, not authored metadata), against a
positive control that finds `object-grid` authored twice in
`examples/app-showcase`. `PageComponentSchema.type` already accepted all
three through its open string arm and still does; what changes is that
an authored props bag on one of them is judged instead of skipped.

## Verification

All readings below are at `e7967ce543` (the branch tip) unless a row
says otherwise, exit codes captured by redirect-then-`$?`, never through
a pipe. The spec suite, typecheck and the generated-artifact checks were
re-run after the patch round below.

| run | verdict |
|---|---|
| `pnpm --filter @objectstack/spec build` | exit 0 |
| `pnpm --filter @objectstack/spec test` | exit 0 — 482 files, 13727
tests passed |
| `pnpm --filter @objectstack/spec typecheck` | exit 0 (`tsc --noEmit` +
`check:scripts-typecheck` + `check:test-typecheck`) |
| `pnpm --filter @objectstack/lint test` | exit 0 — 103 files, 3821
passed, 5 skipped (the `ComponentPropsMap` consumer; its closure built
first) |
| `pnpm --filter @objectstack/lint typecheck` | exit 0 |
| `pnpm --filter @objectstack/spec check:generated` | exit 0 after
`--fix` regenerated the 5 it proved stale |
| `MANIFEST=… check:react-declaration-parity --baseline … --strict` |
exit 0 — `object-map` 2 both / 7 spec-only / **0 registry-only**,
`object-gantt` 2 both / 14 spec-only / **0 registry-only**; baseline
ratchet: no new divergence |
| `pnpm exec eslint . --no-inline-config --format json` | exit 0 —
**6786 files, 0 errors, 0 warnings**, repo-wide, no narrowing |
| 47 further derived gates (`scripts/pm/dispatch-gates.mjs --commands`)
| all exit 0 — see the acceptance notes for the one that did not |

`packages/spec` has no workspace dependencies, so the dependency-closure
build is an empty run by construction.

CI on this head is **not enumerated here** and is not claimed green —
the verdicts above are local runs only.

## Patch round — the at-tier review's two text findings, and the
citations

The contract review returned PASS; these are the three things its scope
did not settle, all of them text or citation, with **no key, no value
shape and no row membership moved**. Verified after the round rather
than asserted: the three key sets read back identical,
`ComponentPropsMap` still carries 48 rows, `object-tree` still accepts
the `ViewData` object arm and still refuses the bare array, the gantt
block is still strict, `object-chart` is still absent.

1. **`object-gantt`'s describes said "chart".** Every sibling names its
own noun, and `object-chart` is the row this section deliberately leaves
absent, so "chart" pointed an author at the one block with no row. Three
describes now name gantt (`objectName`, `label`, `readOnly`). **Proof on
the shipping artifact** — `content/docs/references/ui/component.mdx`,
regenerated with the repo's own `gen:docs`, never by hand: `Object this
chart binds to` → **0**; `Object this gantt binds to` → **1**, at
`:398`; control `Object this NOUN binds to` → **6**, unchanged, so all
six blocks name their own noun exactly once.
2. **`object-tree`'s `objectName` gave a reason that is false for
tree.** It said the component-level `dataSource` binding can supply the
object. Measured at the pin `53ded82b`: `plugin-tree/src/index.tsx` has
**0** hits for the `ElementDataSource*` wiring, against **7** each in
`plugin-map`, `plugin-gantt`, `plugin-grid` and `plugin-calendar` — four
controls, so the zero discriminates. The key stays optional; the reason
is now the true one, the ladder's first two rungs (`data` can name the
object itself, `staticData` needs none), plus the explicit negative. In
the generated doc the false reason reads **0** hits and the new line
**1**, at `:768`.
3. **Two citations corrected**, in the tables above: gantt `label` at
`:1871` (`:1849` is the comment), and tree `data` on `:359` alone.

`check:docs` was red on `e3a256daa3` — source fixed, shipping artifact
not yet regenerated — and is green on `e7967ce543`: `check:docs` exit 0,
"223 generated files in sync with packages/spec"; `check:generated` exit
0, "All 15 generated artifacts are up to date";
`check:authorable-surface` exit 0, which is why `gen:schema` had nothing
to write and `gen:docs` was the only stale artifact. `pnpm --filter
@objectstack/spec test` exit 0, 482 files / 13727 tests, and `typecheck`
exit 0, both re-run after the round. `git diff --name-only
origin/main...HEAD -- content/docs/releases/` → **0** paths, against a
control of **2** under `content/docs/references/`.

On this head `Type Check · source gates` is **completed / success** —
the job the red had truncated now reaches its end. The rest of CI is
still running and is not claimed green.

## Patch round 2 — the at-tier review's FAIL: F1 blocking, F2 and F3 in
passing

Review of record: comment `5696414075` on this PR, `VERDICT: FAIL` at
head `e7967ce543`. That review re-derived the accept set, the public
surface, all three key sets, every value posture and the `minor` level
and judged them RIGHT — nothing in this round moves a key, a value
shape, a row membership or the level. Text, plus the one regeneration it
forces.

**F1 (blocking) — six describes named a function that does not exist in
the code they describe.** The `objectName` / `data` / `staticData`
describes on `object-gantt` and `object-tree` said the record source is
resolved by, or "read FIRST/SECOND by", `getDataConfig`. Re-measured at
the `.objectui-sha` pin `53ded82b`, each file fetched with `git show` in
the same command block as its count:

| file at `53ded82b` | `getDataConfig` | `resolveRecordSourceConfig` |
|:---|---:|---:|
| `plugin-gantt/src/ObjectGantt.tsx` | **0** | 2 — import `:64`, call
`:593` |
| `plugin-tree/src/ObjectTree.tsx` | **0** | 3 — import `:41`, call
`:359`, comment `:422` |
| `plugin-map/src/ObjectMap.tsx` (control) | **8** | 3 |

The control discriminates, and it also settles what the map keeps:
`ObjectMap.tsx` really does hold a local `getDataConfig` at `:135` — the
array-shorthand head, delegating to the shared ladder at `:174` — so the
three map describes are TRUE and keep the name. Gantt and tree reach the
shared three-rung ladder directly: `resolveRecordSourceConfig` in
`@object-ui/core` `utils/record-source.ts:146`, rungs at `:151` (`data`,
returned verbatim), `:155` (`staticData`, wrapped as `{ provider:
'value', items }`) and `:162` (`objectName`, folded to `{ provider:
'object', object }`). So the ladder ORDER those six sentences describe
was already true and only the name was wrong; the six now name
`resolveRecordSourceConfig`. Neither renderer has any reader above its
ladder call, so "read FIRST" is literal for both — unlike the map, whose
array head runs first, which is the second reason its describes keep the
wrapper's name.

**Both sides counted, because this trap already bit this PR once.** An
earlier round fixed a false describe in the source alone and
`check:docs` caught the generated doc still carrying the old text. After
`gen:docs`, at `c431c22e05`:

| | `getDataConfig` | `resolveRecordSourceConfig` |
|:---|---:|---:|
| `packages/spec/src/ui/component.zod.ts` | **3** — map only, `:3226`
`:3242` `:3244` | 11 |
| `content/docs/references/ui/component.mdx` | **3** — map rows only,
`:638-640` | **6** — gantt `:398-400`, tree `:768-770` |

Both sides read 9 and 0 before the round. The generated diff is exactly
six table rows; no map row moved.

**F2** — `component.zod.ts:3545`, the tree `data` field JSDoc, cited two
read points where the schema header at `:3487` already rules that `:496`
is not a second site for the object arm. It cites `:359` alone now.
Source comment; no artifact follows it.

**F3** — two citations off by one line, both re-read at the pin:
`getGanttConfig`'s `safeParse` is `ObjectGantt.tsx:500` (`:501` is the
`if (!result.success)` beneath it), and the tree's "Not authorable"
marker is `ObjectTree.tsx:482`, not `:477`.

### Verification for this round

- `pnpm --filter @objectstack/spec build`, then `check:generated` — exit
0, all 15 artifacts up to date including `check:docs`, with
`content/docs/references/ui/component.mdx` actually regenerated by
`gen:docs` rather than hand-edited. Before the regeneration the same
gate named that one artifact, and only it, stale.
- `pnpm --filter @objectstack/spec test` — exit 0, 482 files / 13727
tests. `pnpm --filter @objectstack/spec typecheck` — exit 0.
- `check:objectui-pin-citations` — exit 0, 26 asserting pin citations
match `53ded82bf`. It verifies the sha, not the line numbers, and
reports 0 anchor content assertions here: F3 was measured by hand at the
pin, not caught by this gate.
- 34 of the 109 gate families `dispatch-gates` derives for this change
set were run locally, every one exit 0; the remaining 75 are the
repo-wide farm CI owns. A declared narrowing, not a silent skip.
- `pnpm lint` narrowed to the diff, with the three readings that make
the narrowing a measurement: the population is eslint's own
`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` and eslint itself answers the
regenerated `.mdx` with "File ignored because no matching configuration
was supplied", so the linted population of this diff is one file;
`--format json` reports 1 file, 0 errors, 0 warnings; and no type-aware
linting is configured anywhere in `eslint.config.mjs` (no
`projectService`, no `parserOptions.project`), so a comment-and-describe
edit in one file cannot move any untouched file's verdict.

## Acceptance notes

### Docs Drift Check, re-verified rather than waved through

The drift check flagged 6 hand-written pages. Each was re-read; none
needs an edit, and the readings are here rather than implied by their
absence from the diff.

- **`content/docs/protocol/objectui/layout-dsl.mdx` does not ride this
PR — measured, not assumed.** It is flagged via the symbol
`ComponentPropsMap`, which it names exactly once, at `:721`, and as a
MECHANISM: *"For the platform's own types the authoring rules dispatch
`ComponentPropsMap` and reject a misspelled prop; a `custom.*` type has
no entry there."* That sentence is still exactly true after this change
— three more types now have entries, and it enumerates none. The page
carries no register of the object-block family at all: no table, no
list, and its only numeric completeness claim is about breakpoints
(`:579`). **The discriminating control:** `object-calendar` and
`object-form` each get **0** hits on that page, and both have carried
`ComponentPropsMap` rows since objectstack-ai#7751 landed on 2026-08-12 — so the page
already omits 2 of the 6 pre-existing rows, against a positive control
of `object-metric` (3), `object-master-detail-form` (3), `object-kanban`
(2), `object-grid` (1). A page that has never tracked the row set is not
a list this change makes incomplete; its `### Metric Widget` / `###
Master-Detail Forms` / `### Kanban Board` sections are chosen worked
examples. Writing three new hand-written renderer sections would be new
documentation of three renderers, which is a card, not a rider on "add
three rows".
- **The other five pages are literal-name collisions, not semantic
links.** `content/docs/ui/views.mdx`, `concepts/architecture.mdx`,
`getting-started/common-patterns.mdx`,
`protocol/kernel/i18n-standard.mdx` and
`ui/field-grouping-and-order.mdx` each get **0** hits for
`ComponentPropsMap` and **0** for `object-map` / `object-gantt` /
`object-tree`. They are reached only through string literals that this
PR's new `OBJECT_GANTT_FLAT_CONFIG_GUIDANCE` key array happens to
contain — `groupByField`, `timeZone`, `colorField`, `startDateField` and
friends. `views.mdx` documents the LIST-VIEW `gantt` block (`type:
'gantt'` with `gantt: { startDateField, … }`, `:340-352`), whose schema
is `ListViewSchema.gantt` / `GanttConfigSchema` in `view.zod.ts` — a
file this diff does not touch at all, so that page's vocabulary is
unmoved.
- **Release-owned pages: untouched, with a control.** The drift check
names 7 pages under `content/docs/releases/`; they are read-only. `git
diff --name-only origin/main...HEAD -- content/docs/releases/` returns
**0** paths, against a control of **2** paths under
`content/docs/references/` in the same diff, so the zero is a reading
and not an empty filter.

### `component-reference-rail.test.ts` needs no change — the reading,
not the omission

The card names it among "the vocabulary tests that pin the row set".
Measured on this branch: the file is 164 lines, mentions
`ComponentPropsMap` 4 times, and **every one of them is the single
subscript `['record:reference_rail']` or prose about that one row**
(`:3`, `:21`, `:33`, `:35`). `Object.keys(ComponentPropsMap)` → **0**
hits; any `object-` type literal → **0** hits. Control, the same two
probes on `component-type-vocabulary.test.ts`, which genuinely does pin
the row set: **6** and **8** ⇒ the zeros discriminate. All twelve of its
`describe`/`it` subjects are `record:reference_rail` behaviour. It reads
no property of the row SET, so nothing in it can move when three
`object-*` rows land — and it passed unchanged inside the 482 green spec
files above.

The row-set pins that DO move with the rows are `component.test.ts` (six
ruled blocks to nine, plus the filter / sort / plural-`filters` /
optional-`objectName` door lists, plus a new describe) and
`component-type-vocabulary.test.ts`. The *reference rail* that
regenerates is the docs one, `content/docs/references/ui/component.mdx`.


### Out of scope for this PR, noted rather than fixed

Each with the measurement that found it:

- **`ListMapConfigSchema` declares no map style at all.** The spec's
list-view map block is strict and has neither `style` nor `mapStyle`,
while `ObjectMap.tsx:365` reads `schema.map?.style` and objectui's
`ObjectMapConfigSchema` declares `style`.
`ListMapConfigSchema.safeParse({ style: '…' })` is refused — pinned as a
negative in this PR's own tests, because it is the reason
`object-map.map` stays `z.unknown()`. An author cannot declare a map
style through the spec's list-view face today. Carrier: this PR's `map`
value ratchet, whenever it is taken.
- **`object-tree` is absent from the tracked `sdui.manifest.json`.**
`check:react-declaration-parity` prints `object-tree: NO component in
the manifest — not registered or not public`, although
`plugin-tree/src/index.tsx` registers it under both `object-tree` and
`tree`. The baseline ratchet is unaffected (a block with no baseline row
cannot regress), so this PR is green either way, but the new row gets no
parity comparison. Carrier: the next `sdui:manifest` regeneration.
- **`pnpm check:cross-package-test-inputs` is sensitive to local build
state, not to this diff.** It exits 1 in a worktree where
`packages/spec/dist/` exists and 0 where it does not — measured here as
a one-variable control, with the diff held constant and the directory
restored byte-identically (216 files, `dist/ui/index.mjs` hash
unchanged). It names
`packages/cli/test/init-created-files-summary.e2e.test.ts`, which
symlinks `packages/spec/dist` at `:115`; this PR touches neither
`packages/cli` nor `turbo.json`. The `Lint & Repo Gates` job runs it on
an unbuilt tree, which is why CI is green on it.
- **`objectui#9239` has moved since the card was written.** The card
describes it as an open, separate calendar-mirror contradiction; it is
`closed completed`. Nothing in this PR depends on it, and
`object-calendar` is untouched.
- **objectui's own gantt face carries the same false attribution F1
removed from ours, and it is pre-existing.** At the same pin `53ded82b`,
`packages/types/src/zod/objectql.zod.ts` docblock `:726` states
`getDataConfig` is in `plugin-gantt/src/ObjectGantt.tsx`, and three
`ObjectGanttSchema` describes repeat the name (`:738`, `:739`, `:838`) —
while that file has 0 occurrences of it (control: `ObjectMap.tsx` 8, and
the map's own mirror describes at `:641-643` are correspondingly TRUE).
Separately, `core/src/utils/record-source.ts` quotes our describes as
the ruled contract at `:28`, `:92`, `:95` and `:97` and attributes the
text to the map and gantt zod twins, so after this PR that quotation
matches the map twin and no longer matches the gantt one. Both sites are
objectui's, in objectui's repo; nothing here can fix them and the pin
does not move. Handed to the seat to file against objectui rather than
fixed in passing.

Not touched, deliberately: the `object-chart` deliberate-absence note in
`component.zod.ts`, pinned as still absent in the new tests.

objectui#8348 carries `pm:blocked` on this card; its remaining slice
judges map / gantt / tree against these rows once this lands. Nothing
here changes any objectui file.

Authored by the `domain:spec` execution seat, session
`session_01KB5PFtxuy1x3dcR5gxudx6`.

---
_Generated by [Claude Code](https://claude.ai/code)_

---------

Co-authored-by: Claude <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — 38df1790 Deployed Jan 23, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants