Skip to content

feat(connector-docusign): DocuSign eSignature connector app over the installer's OAuth client, sandbox first - #29813

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-6yzp2x
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-6yzp2x

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #28277

Clause-②: no
Metadata-protocol: none

维护者速读

What changes

A new npm-private metadata package, @objectstack/connector-app-docusign in packages/apps/connector-docusign, laid out like packages/apps/connector-stripe:

  • openapi/docusign-esignature.openapi.json: DocuSign's eSignature document (docusign/OpenAPI-Specifications esignature.rest.swagger-v2.1.json at 858a3ae59b0edbc8beea4fa3a6d7fe803833dd68, MIT, licence notice in openapi/LICENSE), converted from Swagger 2.0 and trimmed offline by normalize-openapi to Envelopes_PostEnvelopes, Envelopes_GetEnvelope and Envelopes_GetEnvelopes, with the 187 empty consumes lists filled (--empty-consumes application/json). The README's Provenance section records the command, the printed figures and the sha256 the listing test recomputes.
  • src/connectors/docusign.connector.ts: a declarative provider: 'openapi' instance named docusign. providerConfig.baseUrl points it at the sandbox API host (https://demo.docusign.net/restapi), and include names the three operations. auth is oauth2 authorization_code against account-d.docusign.com with scopes: ['signature'] and clientSource: 'installer'. No clientId and no credentialRef: per ruling 6566728470 (letter B), the administrator of each deployment registers their own DocuSign integration key against that deployment's redirect URI and enters it on the connection. No key value is in the repository.
  • src/objects/docusign-envelope.object.ts and src/views/docusign-envelope.view.ts: docusign_envelope with the two fields the skill eval names, envelope_id and signature_status, plus email_subject and status_changed_at, and one grid list view.
  • src/flows/docusign.flows.ts: docusign_send_envelope (create and send one envelope, record it only when DocuSign accepted it), docusign_get_envelope (read one envelope, refresh its record), and docusign_poll_status_changes (list changes since from_date, copy each status onto its record; each update contained in a try_catch).
  • objectstack.manifest.json with an iconUrl at the package's own assets/icon.svg, README.md (what it adds, credentials, running it, callbacks, limits, provenance), and test/listing.test.ts plus test/boot.test.ts.
  • pnpm-lock.yaml, regenerated by pnpm install only. Besides the new importer, pnpm re-recorded knex@3.3.0's optional peers (mysql2, pg, tedious, already in the lock) on its two importers. A pnpm install --lockfile-only on clean origin/main f87239ae changes nothing, while the same command with this importer added writes exactly this diff, so the peer re-resolution is pnpm's own response to the importer set changing.
  • packages/spec/src/system/constants/platform-object-names.test.ts: the platform-object registry scan now leaves out installable app listings (a package root carrying objectstack.manifest.json), with two pin cases: the exclusion keys on the listing file and nothing broader (a lit control sees docusign_envelope and keeps it out), and an installable listing may declare no platform-prefixed object. The platform apps under packages/apps (account, setup, studio) carry no listing and stay scanned. Nothing is added to PLATFORM_OBJECTS_BY_PACKAGE; the file is outside spec's files[], so skip-changeset still holds.

No changeset: the package is private: true and publishes nothing, so this PR carries skip-changeset.

Why

#28277 is E4 batch 2 of #27293: DocuSign as the first declarative verification of the outbound OAuth path. Its blocker was the OAuth client. A packaged clientId works only on deployments whose callback the client's owner registered (report 6559204204). Ruling 6566728470 chose letter B, executed as #28864 (spec, clientSource: 'installer' on ConnectorInstanceOAuth2AuthorizationCodeAuthSchema, packages/spec/src/shared/connector-auth.zod.ts), #28866 (server: installerClientOfBegin and keptInstallerClient in packages/services/service-automation/src/plugin.ts, takesInstallerClient in oauth2-grant/installer-client.ts) and #28869 (the Console's Connect dialog). This package is the first listing on that shape.

Tests

pnpm --filter @objectstack/connector-app-docusign test at head d9a09f83: objectstack validate exit 0 (1 object, 4 fields, 1 view, 3 flows, no warning), then vitest Test Files 2 passed (2), Tests 27 passed (27) (lock VERDICT command-exit 0). pnpm --filter @objectstack/connector-app-docusign typecheck exit 0.

  • test/listing.test.ts (17): the manifest parses against TemplateManifestSchema, names the stack id and an owned http(s) icon; the connector's auth is exactly the installer-client shape and parses against DeclarativeConnectorEntrySchema; no file the package owns (outside DocuSign's document) carries a GUID-shaped value; the README's Credentials and Callbacks sections name the redirect URI path, the connection door and the poll flow; the packaged document's sha256 equals the README's record, it is OpenAPI 3.x with no security scheme, carries exactly the three included operations, and gives Envelopes_PostEnvelopes a JSON body.
  • test/boot.test.ts (10): booted with bootStack against a fake DocuSign (the API through ConnectorOpenApiPlugin's fetchImpl, the sandbox token endpoint through a fetch stub). Before a connect the instance is degraded "not connected" and the connection read answers redirectUri; a flow run fails FLOW_FAILED naming the connector and sends nothing; a connect naming no client is refused 400 VALIDATION_ERROR naming the vendor registration step and the redirect URI; a connect with the administrator's integration key sends the browser to account-d.docusign.com/oauth/auth as that client (scope=signature, code_challenge_method=S256), the callback exchanges the code in HTTP Basic as that client, and the read shows the client without its secret; the instance then materializes with exactly the three actions; the three flows send the expected requests to demo.docusign.net as the bearer of the connection's token and write or refresh the record; a refused envelope writes no record.

pnpm --filter @objectstack/spec test:repo at head a7e2abd1: 89 files and 1524 tests passed. Reverse verification: the pre-fix platform-object-names.test.ts fails 1 of 12 on this tree with the CI message; the fixed file passes 14 of 14.

No other ablation: the rest of the diff adds a package and changes no tested behaviour of existing code.

Acceptance notes

  • Live sandbox round trip: NOT MEASURED, owed to the acceptance run. curl https://account-d.docusign.com/.well-known/openid-configuration from this container: the egress proxy answered the CONNECT with 403 Forbidden (demo.docusign.net and developers.docusign.com: no answer either). No integration key exists for this repository either, by design. The DocuSign console steps in the README follow DocuSign's documentation and say so.
  • Document depth. The package ships the document at --max-depth 0 (304,080 bytes, sha256 b741ded5cfc4d0291d7f0e26559f8583f8c9a783f8711d8f9a98405cb27f7cb4, 1,438 lines), as connector-stripe does. At the default depth of 2 the same run writes 553,922 bytes, sha256 e1051eb4bbc381d879e979beac8cb64ead24531c93a19fc26e950a12d9fc961d (the epic probe's hash, re-derived here independently), 4,737 lines, which takes this PR to 6,130 changed lines, over the 3,000-line human-merge threshold. At depth 0 the PR is 2,841 changed lines. The cost: Envelopes_PostEnvelopes's body is described as DocuSign's envelope definition (type and description) rather than with its fields expanded. Switching is one normalizer run plus the README figures and one listing-test assertion.
  • Sandbox only. Production DocuSign signs in at account.docusign.com and has a per-account API host (base_uri from /oauth/userinfo) that a packaged instance cannot name; the README's Limits section says production is not supported yet. No follow-up card is filed here.
  • Callbacks. DocuSign Connect signs its own HMAC header, which the api flow door does not verify, so the app takes no Connect posts and polls instead (docusign_poll_status_changes); the README says so.
  • CEL map typing. A CEL map literal's values share one type, so every string literal beside a variable read is wrapped in dyn(…) at every nesting level; os validate refused the first draft that wrapped only the top level (expression-invalid, "Map value uses wrong type"). The Stripe README already states the rule for the top level.

Verification

  • node scripts/check-package-naming.mjs: exit 0 (129 workspace package(s), the @objectstack/plugin-* set exactly the 10 frozen names; this package is @objectstack/connector-app-docusign, beside connector-app-stripe and connector-app-github).
  • node scripts/pm/dispatch-gates.mjs --commands on this diff at head a7e2abd1 derived 85 families; all 85 ran, every one exit 0 (four spec dist gates re-run after a spec build); --ran reconciliation: 85 run, 0 NOT-MEASURED. Changed lines: 2923.
  • pnpm lint (eslint . --no-inline-config): exit 0 (on d9a09f83; the patch round changes one test file).
  • Every other CI family: this PR's check-runs.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No spec change.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (Publishes nothing: skip-changeset.)
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (Not touched.)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 block excepted, by the dispatch contract).

🤖 Generated with Claude Code

https://claude.ai/code/session_01JQwxbX1cvwSxDEYCkfDcLg


Generated by Claude Code

…installer's OAuth client

A declarative `openapi` instance over DocuSign's eSignature document,
trimmed offline to three envelope operations, authorized by OAuth 2.0
`authorization_code` with `clientSource: 'installer'` (no client of
anyone's in the package), sandbox first; an envelope object and three
flows (send, get, poll status changes in place of Connect callbacks).

Claude-Session: https://claude.ai/code/session_01JQwxbX1cvwSxDEYCkfDcLg
Co-Authored-By: Claude <noreply@anthropic.com>
… README and test tightening

Claude-Session: https://claude.ai/code/session_01JQwxbX1cvwSxDEYCkfDcLg
Co-Authored-By: Claude <noreply@anthropic.com>
…onnector-stripe does

Claude-Session: https://claude.ai/code/session_01JQwxbX1cvwSxDEYCkfDcLg
Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 3, 2026
@github-actions github-actions Bot added size/xl dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests tooling labels Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/connector-app-docusign, touching 58 documentable anchor(s). ⚠️ 8 changed file(s) yielded no anchor (packages/apps/connector-docusign/README.md, packages/apps/connector-docusign/assets/icon.svg, packages/apps/connector-docusign/objectstack.config.ts, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

65 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json b0ef25f524b870433424b8ad797ef3801374f745.

⛔ 13 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 8 changed file(s) yielded no anchor (packages/apps/connector-docusign/README.md, packages/apps/connector-docusign/assets/icon.svg, packages/apps/connector-docusign/objectstack.config.ts, …) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: sharingModel (symbol, 47 pages)
  • 8 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 103 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json b0ef25f524b870433424b8ad797ef3801374f745 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 66b4ddcad6b548235855a5615ab9d6b5dec66699 — the merge of head a7e2abd1d461730ee8da94e6cebbc3c06aa8d5ff into base b0ef25f524b870433424b8ad797ef3801374f745, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 66b4ddcad6b548235855a5615ab9d6b5dec66699 && git checkout 66b4ddcad6b548235855a5615ab9d6b5dec66699
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b0ef25f524b870433424b8ad797ef3801374f745 a7e2abd1d461730ee8da94e6cebbc3c06aa8d5ff && git checkout -B drift-repro b0ef25f524b870433424b8ad797ef3801374f745 && git merge --no-ff a7e2abd1d461730ee8da94e6cebbc3c06aa8d5ff

node scripts/docs-audit/affected-docs.mjs --json b0ef25f524b870433424b8ad797ef3801374f745

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs b0ef25f524b870433424b8ad797ef3801374f745 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…listings out

An app package with an `objectstack.manifest.json` listing at its root is
installed like a third-party app; its objects are its own, not
platform-provided. The scan now keys the exclusion on that file (the
platform apps under packages/apps carry none and stay scanned), pins it,
and refuses a listing that declares a platform-prefixed object.

Claude-Session: https://claude.ai/code/session_01JQwxbX1cvwSxDEYCkfDcLg
Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-warren · session_01JQwxbXKRzgnVvjzxaYxLUf

维护者速读

席位核对

ACCEPT · domain:server#2 · head a7e2abd1d461730ee8da94e6cebbc3c06aa8d5ff · 2026-11-03T06:54Z · seat review of record at TIER_DEFAULT (not governed: check-governed-merges --pr 29813 0 of 17 paths; Clause-②: no; skip-changeset, private package; the one packages/spec path is a conformance test, no schema file; no contract face, so no second agent)

  1. Shape: draft, base main, Fixes #28277, Clause-②: no, Metadata-protocol: none, skip-changeset ("private": true). 17 files, +2911/-12, 2923 changed lines (under the 3000 human-merge threshold). Package @objectstack/connector-app-docusign, named like @objectstack/connector-app-stripe; check-package-naming exit 0. Seat read the connector: provider: 'openapi', include = the three operations (Envelopes_PostEnvelopes, Envelopes_GetEnvelope, Envelopes_GetEnvelopes), sandbox baseUrl demo.docusign.net, auth oauth2 authorization_code against account-d.docusign.com with scopes: ['signature'] and clientSource: 'installer' (the spec: an oauth2 authorization_code instance takes its OAuth client from the installer (a per-connection client record), not a packaged clientId — ADR-0143 D2.1 amendment (#28702 B) #28864/automation: a sys_connector_connection stores and uses the installer-supplied OAuth client for an authorization_code instance (#28702 B, server slice) #28866 shape on main), no clientId, no credentialRef, no key value anywhere: ruling B (6566728470) held.
  2. Premise measured: every Blocked-by: closed; the eSignature document is docusign/OpenAPI-Specifications (MIT, LICENSE packaged); the depth-2 run re-derived the epic seat's sha e1051eb4…961d; the packaged cut is --max-depth 0, the Stripe precedent, recorded with its sha in the README. Seat call on the deviation: depth 0 accepted by that precedent (the cost is a typed stub for the PostEnvelopes body; the eval's three operations all materialize).
  3. Gates on this head: check-runs pinned to a7e2abd1: 65 total, 57 success, 8 skipped, 0 failed (Test Core 2/8 was red on d9a09f83: the spec platform-object-name conformance scan counted docusign_envelope as a platform object). Dev: 62/62 derived families exit 0, --ran reconciled, pnpm lint 0; os validate green; vitest 27/27, the boot test driving the installer-client connect against a fake DocuSign.
  4. The scanner fix (a7e2abd1, packages/spec/src/system/constants/platform-object-names.test.ts +73/-9): the exclusion is keyed on the package's own objectstack.manifest.json listing, not on packages/apps/, so the platform apps account, setup, studio stay scanned; a lit control proves docusign_envelope is seen and kept out, and a new guard refuses any installable app object under a reserved platform prefix, so the exclusion cannot hide a sys_* object. Seat read: narrow, as asked.
  5. Prose checked against the diff: the docblock's recipe reference is recipe 6 ("OAuth or vendor-app token — the server runs the grant") of skills/objectstack-automation/references/integration-recipes.md on main; the README's Credentials section says the administrator registers their own integration key against their deployment's redirect URI, as clientSource: 'installer' requires; Callbacks says DocuSign Connect signs its own header, which the api door does not verify, so the app polls status.
  6. Out of scope / Acceptance notes: the live sandbox round trip is NOT MEASURED (the container's proxy answered 403 on account-d.docusign.com) and is owed to the acceptance run. A production account's API host is per account (base_uri from /oauth/userinfo), so this listing is sandbox-first as ruled. security_findings: none. Skills-docs: not owed.
  7. Landing: not governed, under 3000 lines ⇒ the seat lands it (pr_ready + automerge_enable). At MERGED: landing record on connector apps, E4 batch 2 (OAuth): DocuSign as the first declarative verification of the outbound OAuth path, an open-edition metadata package #28277, pm:dispatched removed (pm:epic kept), card thread released.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 3, 2026 06:55
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 3, 2026 06:55
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit c5ddbea Nov 3, 2026
70 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-6yzp2x branch November 3, 2026 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation protocol:system size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

connector apps, E4 batch 2 (OAuth): DocuSign as the first declarative verification of the outbound OAuth path, an open-edition metadata package

1 participant