Skip to content

Add missing Field.phone() helper and factory methods for Action/Dashboard/Report - #15

Merged
huangyiirene merged 5 commits into
copilot/add-crm-functionality-examplefrom
copilot/fix-action-job-step-error
Jan 19, 2026
Merged

huangyiirene merged 5 commits into
copilot/add-crm-functionality-examplefrom
copilot/fix-action-job-step-error

Conversation

Copilot AI commented Jan 19, 2026

Copy link
Copy Markdown
Contributor

Fix CRM Example TypeScript Build Errors ✅

Problem: The CRM example has multiple TypeScript compilation errors preventing CI from passing.

Root Causes:

  1. Field.select() signature mismatch - expects string[] but code passes SelectOption[] objects
  2. Missing phone field type helper in Field factory
  3. Action, Dashboard, and Report are only types, not runtime objects with .create() methods

Solution:

  • Add phone field type helper to Field factory
  • Update Field.select()/Field.multiselect() to support both old and new API signatures (backward compatible)
  • Add factory methods (Action.create, Dashboard.create, Report.create) using z.input for proper default handling
  • Add comprehensive test coverage (12 new tests)
  • Add JSDoc comments documenting the API
  • Fix spread order in select/multiselect to avoid option duplication
  • Security scan: ✅ No vulnerabilities
  • Code review: ✅ Addressed feedback

Backward Compatibility:

// Old API (still works)
Field.select(['High', 'Low'], { label: 'Priority' })

// New API (now supported)
Field.select({ 
  options: [{label: 'High Priority', value: 'high', color: '#FF0000'}], 
  label: 'Priority' 
})

Test Results: ✅ All 279 tests passing
Build Status: ✅ All packages build successfully (CRM, TODO, docs)
Security: ✅ No vulnerabilities detected

Original prompt

引用: https://github.com/objectstack-ai/spec/actions/runs/21135959933/job/60779299734#step:8:1


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

@vercel

vercel Bot commented Jan 19, 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 19, 2026 0:12am

Request Review

- Add phone field type helper to Field factory
- Update Field.select/multiselect to support both array and object signatures
- Add factory methods (Action.create, Dashboard.create, Report.create)
- Use z.input and parse for factory methods to support default values

Co-authored-by: huangyiirene <[email protected]>

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 fixes TypeScript compilation errors in the CRM example by enhancing the Field factory API and adding factory helper methods for UI components. The changes enable both legacy string-array syntax and new object-based configuration for select fields, while adding missing field type helpers and runtime factory methods.

Changes:

  • Added phone field type helper and dual-signature support for select/multiselect fields (backward compatible)
  • Added factory helper methods (Action.create, Dashboard.create, Report.create) with proper type inference using z.input
  • Restructured documentation metadata hierarchy by moving root: true from parent to child sections

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/spec/src/data/field.zod.ts Added phone helper and refactored select/multiselect to accept both string arrays and SelectOption objects
packages/spec/src/ui/action.zod.ts Added Action.create() factory method with additional exported types
packages/spec/src/ui/dashboard.zod.ts Added Dashboard.create() factory method with exported widget type
packages/spec/src/ui/report.zod.ts Added Report.create() factory method with additional exported types
content/docs/references/meta.json Removed root: true from parent metadata
content/docs/references/data/meta.json Added root: true to Data Protocol section
content/docs/references/ui/meta.json Added root: true to UI Protocol section
content/docs/references/system/meta.json Added root: true to System Protocol section
content/docs/references/ai/meta.json Added root: true to AI Protocol section

Comment thread packages/spec/src/data/field.zod.ts Outdated
finalConfig = optionsOrConfig;
}

return { type: 'select', ...finalConfig, options } as const;

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

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

The spread order ...finalConfig, options means that if finalConfig contains an options property, it will be overwritten by the options variable. Consider using options, ...finalConfig to ensure the parsed options take precedence, or explicitly handle the options property separately to avoid confusion.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 49030d1. Now extracting options from config object and placing processed options first in spread order to ensure they always take precedence.

Comment thread packages/spec/src/data/field.zod.ts Outdated
finalConfig = optionsOrConfig;
}

return { type: 'multiselect', ...finalConfig, options } as const;

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

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

The spread order ...finalConfig, options means that if finalConfig contains an options property, it will be overwritten by the options variable. Consider using options, ...finalConfig to ensure the parsed options take precedence, or explicitly handle the options property separately to avoid confusion.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 49030d1. Now extracting options from config object and placing processed options first in spread order to ensure they always take precedence.

@huangyiirene

Copy link
Copy Markdown
Collaborator

@copilot fix

- Add tests for Field.phone() helper
- Add tests for Field.select() backward compatibility (old and new API)
- Add tests for Field.multiselect() backward compatibility
- Add tests for Action.create() with default value handling
- Add tests for Dashboard.create() with default value handling
- All 279 tests passing

Co-authored-by: huangyiirene <[email protected]>
Document backward-compatible API with examples for both old and new signatures

Co-authored-by: huangyiirene <[email protected]>
Copilot AI changed the title [WIP] Fix action job step error Add missing Field.phone() helper and factory methods for Action/Dashboard/Report Jan 19, 2026
Copilot AI requested a review from huangyiirene January 19, 2026 12:08
- Extract options from config object to avoid confusion
- Change spread order to options first, then finalConfig
- Ensures processed options always take precedence

Co-authored-by: huangyiirene <[email protected]>

Copilot AI commented Jan 19, 2026

Copy link
Copy Markdown
Contributor Author

@copilot fix

Fixed the spread order issue in select/multiselect field helpers (commit 49030d1). The options property is now extracted from the config object and the processed options array always takes precedence.

@huangyiirene
huangyiirene marked this pull request as ready for review January 19, 2026 12:16
@huangyiirene
huangyiirene merged commit 8beab63 into copilot/add-crm-functionality-example Jan 19, 2026
2 checks passed
os-zhuang pushed a commit that referenced this pull request Jun 20, 2026
…table #15)

The verification table cited permission-evaluator.ts for the role-hierarchy no-op;
the accurate evidence is sharing-rule-service.ts expandRecipient (no role_and_subordinates
branch) + flat team-graph + no Role.parent consumer. Substance (widening unimplemented)
unchanged; citation corrected after re-verification.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx
os-zhuang added a commit that referenced this pull request Jun 20, 2026
…del enforce/prove/reconcile) (#2058)

* docs(adr): ADR-0056 permission model landing verification (whole-model enforce/prove/reconcile audit)

A capstone governance ADR that applies the ADR-0049 (enforce-or-remove) and
ADR-0054 (prove-it-runs) gates to the ENTIRE authorization model for the first
time. Based on a four-pass read-only audit (spec inventory, runtime-enforcement
trace, proof-coverage matrix, prior-ADR reconciliation), it presents a per-primitive
declared↔enforced↔proven verification table and makes 10 reconciling decisions:

- D1 collapse the 3-way-mismatched OWD enum to one canonical set + reject unknown
- D2 close the anonymous default-allow fail-open (default-deny)
- D3 OWD baseline must not depend on plugin-sharing being loaded
- D4 RLS compiler must error at authoring time / fail-closed at runtime, never silently drop
- D5 reconcile Sharing Rules spec↔runtime divergence per ADR-0049
- D6 role-hierarchy widening: implement or mark experimental (it is a silent no-op today)
- D7 first-class app-declared default Profile (replaces hardcoded fallback)
- D8 triage compliance/encryption/masking surface per ADR-0049
- D9 bind enforced-but-unproven invariants into the liveness ledger
- D10 (durable) an Authorization Conformance Matrix as a CI-checked artifact

Phased P1 honesty-pass → P2 close fail-open holes → P3 provisioning+proofs → P4 evidence-gated.
Status: Proposed. No code change; design only.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx

* docs(adr): ADR-0056 — correct role-hierarchy evidence citation (D6 / table #15)

The verification table cited permission-evaluator.ts for the role-hierarchy no-op;
the accurate evidence is sharing-rule-service.ts expandRecipient (no role_and_subordinates
branch) + flat team-graph + no Role.parent consumer. Substance (widening unimplemented)
unchanged; citation corrected after re-verification.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx

---------

Co-authored-by: Claude <[email protected]>
This was referenced Sep 5, 2026
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…he response (objectstack-ai#18144)

Closes objectstack-ai#17984

`Clause-②: no`

## The defect, and the premise that turned out to be false

On PR objectstack-ai#17982 `Auto Label` went red for work it had **completed**. Step
objectstack-ai#3 POSTed `tests`, the API answered HTTP 500, the script exited 1 — and
the PR's label set read `size/s, skip-changeset, tests` immediately
afterwards. A 500 is not evidence the write failed.

The job's red said *"the response failed"*; every reader takes it to
mean *"the label is missing"*. Those are different facts, and that gap
is the defect.

⚠️ **One premise in the card is false, and it is worth stating
plainly.** The card reads "**No retry on 5xx.** A 500 from the GitHub
API is transient by definition". `scripts/pr-labels.mjs` has had a
**bounded exponential-backoff retry on 5xx since the day the file
landed** (objectstack-ai#10777, 2026-08-22) — 4 attempts, `2 ** attempt * 500` ms,
with 4xx-other-than-429 breaking out as fatal. Verified against the
tree: the file's last commit before the incident is 2026-09-06, and the
loop is present in the 2026-08-22 blob.

So **two of the card's three suggested-shape bullets were already
implemented** (retry-on-5xx, 4xx-stays-fatal). The card's incidental
observation that "the same window produced at least four" 500s is most
likely *those four retry attempts*: `runPlan` logs the POST once and
`ghRequest` reports only `lastError`, so four internal attempts render
as exactly the one-POST-one-500 log the card read.

The one bullet that was genuinely missing is the one the card itself
identified as the actual defect: **idempotency-awareness**. Nothing ever
re-read the board.

## What changed

One file, `scripts/pr-labels.mjs`. Failures are now **classified**
rather than uniformly fatal:

| class | statuses | treatment |
|:--|:--|:--|
| **INDETERMINATE** — the server may have acted before the answer was
lost | any `5xx`; a fetch that **threw** | re-read the PR's labels and
judge the step's **post-condition** |
| **DETERMINATE** — the server refused and did not act | `4xx`, **`429`
included** | fatal, loud, byte-identical error message to before |

Three new pure exports carry the decision:

- `failureIsIndeterminate({ status, threw })`
- `postconditionOf(step)` — reads the wanted state **off the step
itself** (`POST` ⇒ its labels present; `DELETE` ⇒ the one named label
absent), so it cannot drift from what the step asks for
- `settleWriteFailure({ step, liveLabels, indeterminate })`

`ghRequest` now tracks indeterminacy **stickily across attempts** — if
any attempt could have reached the server's state, the whole request is
indeterminate even when a later attempt came back with a clean 4xx.
`runPlan` routes a failed write through `settleOrRethrow`.

Two deliberate non-relaxations:

- a **determinate 4xx stays fatal even when the board satisfies the
post-condition**. A `403` is a broken token and a `422` is a label that
does not exist in the repo; the label being there by some other hand
does not make the token work.
- a **settling re-read that itself fails settles nothing** — the write
is reported `UNVERIFIED`, and the *original* error is raised (AGENTS.md
*Route & surface ownership* §3: absence must be loud, prefer failing to
falling back).

## The two-direction test, and its verdicts

`--self-test` gains a 16-case battery, `the objectstack-ai#17982 indeterminate write,
settled against the board`, pinned in `SELF_TEST_BATTERIES`;
`SELF_TEST_BATTERY_FLOOR` goes 6 → 7 so the new battery cannot be
silently deleted.

A battery that only proved the settle succeeds where the write landed
would be the same exit-0-by-construction shape this card is about. So
**both legs were ablated**, each proven to land on disk by an occurrence
count on the mutated anchor before the run:

| ablation | anchor before → after | self-test |
|:--|:--|:--|
| **A** — delete the 4xx-stays-loud leg (`if (!indeterminate)` → `if
(false)`) | 1 → 0 old, 1 new | **exit 1**, `FAIL a determinate 4xx is
NOT settled, even with the label present` · `FAIL …and it says so in
those words` |
| **B** — make 5xx read as determinate (`return Number(status) >= 500` →
`return false`) | 1 → 0 old, 1 new | **exit 1**, `FAIL a 500 is
indeterminate` · `FAIL so is a 503` |

Both restores were settled by `git hash-object` against the HEAD blob
(`24e4c5098d6b…`, matched) plus an empty `git diff HEAD` — ⛔ not by a
`trap`, and not by a restore command's exit code. Unmutated verdict:
`VERDICT: pr-labels self-test PASSED`.

## The sweep — population, criterion, controls

The card asked for the population, not a "I also checked others".

**Criterion counted by:** a tracked file that (a) names a GitHub API
host (`api.github.com` / `GITHUB_API_URL`) **and** (b) issues a non-GET
verb, then judged on whether its write failure handling **treats the
response status as the verdict with no post-condition re-read**.

**Population:** `git grep -lE "api\.github\.com|GITHUB_API_URL"` over
tracked files → **23** under `scripts/**`, **3** elsewhere
(`.claude/hooks/guard-governed-enqueue.{sh,selftest.sh}`,
`.claude/settings.json`), **0** under `packages/**`. Of the 23, **7**
contain a write verb.

**Controls.** Firing control: `scripts/pr-labels.mjs` — the known
positive, still present, 38 953 bytes — appears in both the population
and the write-verb narrowing. Nonsense control: the same probe for
`api.gitlab.com|GITLAB_API_URL` reads **0**.

| file | verdict |
|:--|:--|
| `scripts/check-whole-set-label-write.mjs` | **not a writer.** Its 8
verb hits are literals in its own detector vocabulary and fixtures.
Excluded with evidence. |
| `.claude/settings.json`,
`.claude/hooks/guard-governed-enqueue.selftest.sh` | **not writers.**
Verb hits are *matcher patterns* for a read-only guard hook (the hook
itself: 0 verb hits). |
| `scripts/pm/label-write.mjs` | ⭐ **already correct, and it is the
in-repo reference for this cure.** Nothing there throws on an HTTP
status; `classifyHttp` routes, and exit 0 requires *"the write landed
AND the read-back matched the target"*, with a second read-back after a
re-add. |
| `scripts/pm/sweep-stale-finding.mjs` | **deliberate, documented
no-retry design** — stops on the first `403`/`429` and prints a resume
cursor; carries a `read-back-mismatch` stop reason. Not this shape. |
| `scripts/pm/post-stamped.mjs` | **has the shape.** Bare `if (!res.ok)
throw` in `rest()`, no retry, no post-condition re-read; its existing
read-back verifies stored *bytes* on the success path only. |
| `scripts/pm/sweep-closed-cards.mjs` | **has the shape.**
Byte-identical `rest()` helper to the above. |
| `scripts/release-github-releases.mjs` | **has the shape** on
`POST`/`PATCH` release. Release-lane, Prime Directive objectstack-ai#15 territory. |
| `.github/workflows/**` inline (5 files) | `github.rest.issues.*` via
`actions/github-script`; **not swept further** — out of this card's file
surface. |

**What was fixed vs. left as a card candidate.** Fixed:
`scripts/pr-labels.mjs` only. ⛔ Deliberately **not** extracted into a
shared helper and swapped into the other three: they are **seat-invoked
CLI tools**, where a human or agent reads the output and re-runs, not
unattended CI jobs whose red blocks a PR — the severity that makes this
card worth fixing does not carry over, and `scripts/pm/label-write.mjs`
already shows the repo has the discipline where the stakes are highest.
The three are reported to the PM as a **card candidate**, not widened
into this PR.

## Reverse-read, both directions

- **Currently-true sentence this makes false:**
*"`scripts/pr-labels.mjs` fails the job on any non-2xx answer from the
labels API."* Now false for the 5xx/thrown class — false only when the
board proves the post-condition holds.
- **Currently-false sentence this makes true:**
*"`scripts/pr-labels.mjs` re-reads the PR's labels after a failed write
and judges the write by the board's state."*
- **A zero, reported as required:** the change moves **no** sentence
about 4xx behaviour. A `4xx` threw and exited 1 before, and rethrows the
**same error object with the same message** now. That invariance is
asserted by ablation A rather than asserted in prose.

## Scope, gates, publishing

- ⛔ **This PR does NOT touch `.github/workflows/**`.** The fix is
entirely inside the script, which both `lint.yml` and
`pr-automation.yml` already invoke via `--self-test`. No workflow arming
problem for the PM seat.
- **Gates:** `node scripts/pm/dispatch-gates.mjs --commands` derived
**32** families from the change set at the final tree; all 32 run, **all
exit 0**. `--ran` reconciliation with per-command exit codes: `✓ 32
derived famil(ies) accounted for — 32 run, 0 NOT-MEASURED (a DERIVED
zero)`.
- **Lint:** the full repo-wide union was run rather than narrowed —
`eslint . --no-inline-config --format json` over **6 751** files, **0
errors, 0 warnings**, exit 0, at `1bc65ff50e`. (`eslint.config.mjs`
declares no `parserOptions.project` and no typed rules, so the narrowing
question is moot in any case.)
- **`skip-changeset`, measured not asserted:** every
`pnpm-workspace.yaml` glob roots under `packages/*` / `apps/*` /
`examples/*`; npm `files[]` resolves relative to a package dir and
cannot reach outside it; `scripts/pr-labels.mjs` sits at the repo root
inside **no** package, and the root manifest is `private: true`.
Positive control: `packages/spec` is non-private with a real `files[]`,
so the probe can distinguish. ⇒ nothing publishes ⇒ label, not a
changeset.

## 验收备注

- noted, not filed: `ghRequest`'s 4-attempt / `2 ** attempt * 500` ms
budget is a hard-coded literal with no env override. Not a defect and
not in scope; whoever next tunes the labeler's patience will meet it.
承接者:无 — no queued PR touches this file.

---
🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU


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

Co-authored-by: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants