@firedrill-tools/netsuite 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +214 -0
- package/firedrill/agent.target.json +16 -0
- package/firedrill/baseline.scenario.json +6905 -0
- package/firedrill/concurrency-limited.scenario.json +11 -0
- package/firedrill/conformance.suite.json +21 -0
- package/firedrill/ns-analytics.drill.json +73 -0
- package/firedrill/ns-concurrency.drill.json +53 -0
- package/firedrill/ns-default-identity.drill.json +43 -0
- package/firedrill/ns-denied.drill.json +38 -0
- package/firedrill/ns-insufficient-permission.drill.json +53 -0
- package/firedrill/ns-invalid-login.drill.json +53 -0
- package/firedrill/ns-order-to-cash.drill.json +93 -0
- package/firedrill/ns-record-locked.drill.json +53 -0
- package/firedrill/ns-records.drill.json +68 -0
- package/firedrill/ns-role-ar-clerk.drill.json +58 -0
- package/firedrill/ns-role-sales-rep.drill.json +58 -0
- package/firedrill/ns-subsidiary-scope.drill.json +58 -0
- package/firedrill/ns-tight-limits.drill.json +58 -0
- package/firedrill/ns-transform-lost.drill.json +63 -0
- package/firedrill/ns-write-outage.drill.json +63 -0
- package/firedrill/record-locked.scenario.json +11 -0
- package/firedrill/tight-limits.scenario.json +17 -0
- package/firedrill/tools/netsuite/app/assets/ATTRIBUTION.md +37 -0
- package/firedrill/tools/netsuite/app/assets/fonts/LICENSE.txt +92 -0
- package/firedrill/tools/netsuite/app/assets/oracle-netsuite.svg +1 -0
- package/firedrill/tools/netsuite/app/assets/oracle.svg +1 -0
- package/firedrill/tools/netsuite/app/site/app.js +182 -0
- package/firedrill/tools/netsuite/app/site/assets/fonts/open-sans-latin-ext.woff2 +0 -0
- package/firedrill/tools/netsuite/app/site/assets/fonts/open-sans-latin.woff2 +0 -0
- package/firedrill/tools/netsuite/app/site/assets/oracle-netsuite.svg +1 -0
- package/firedrill/tools/netsuite/app/site/assets/oracle.svg +1 -0
- package/firedrill/tools/netsuite/app/site/icons.js +59 -0
- package/firedrill/tools/netsuite/app/site/index.html +68 -0
- package/firedrill/tools/netsuite/app/site/menus.js +111 -0
- package/firedrill/tools/netsuite/app/site/overlay.js +121 -0
- package/firedrill/tools/netsuite/app/site/store.js +95 -0
- package/firedrill/tools/netsuite/app/site/styles/base.css +131 -0
- package/firedrill/tools/netsuite/app/site/styles/chrome.css +183 -0
- package/firedrill/tools/netsuite/app/site/styles/pages.css +134 -0
- package/firedrill/tools/netsuite/app/site/styles/tables.css +92 -0
- package/firedrill/tools/netsuite/app/site/ui.js +119 -0
- package/firedrill/tools/netsuite/app/site/views/common.js +134 -0
- package/firedrill/tools/netsuite/app/site/views/customer-form.js +152 -0
- package/firedrill/tools/netsuite/app/site/views/customer.js +147 -0
- package/firedrill/tools/netsuite/app/site/views/customers.js +111 -0
- package/firedrill/tools/netsuite/app/site/views/home-chrome.js +75 -0
- package/firedrill/tools/netsuite/app/site/views/home-data.js +153 -0
- package/firedrill/tools/netsuite/app/site/views/home.js +162 -0
- package/firedrill/tools/netsuite/app/site/views/invoice-new.js +131 -0
- package/firedrill/tools/netsuite/app/site/views/invoice.js +232 -0
- package/firedrill/tools/netsuite/app/site/views/invoices.js +99 -0
- package/firedrill/tools/netsuite/app/site/views/items.js +95 -0
- package/firedrill/tools/netsuite/app/site/views/order-new.js +154 -0
- package/firedrill/tools/netsuite/app/site/views/order.js +214 -0
- package/firedrill/tools/netsuite/app/site/views/orders.js +93 -0
- package/firedrill/tools/netsuite/app/site/views/payments.js +65 -0
- package/firedrill/tools/netsuite/app/site/views/pickers.js +117 -0
- package/firedrill/tools/netsuite/app/site/views/subsidiaries.js +40 -0
- package/firedrill/tools/netsuite/app/site/views/suiteql.js +81 -0
- package/firedrill/tools/netsuite/behavior.mjs +13 -0
- package/firedrill/tools/netsuite/lib/body.mjs +185 -0
- package/firedrill/tools/netsuite/lib/collections.mjs +202 -0
- package/firedrill/tools/netsuite/lib/errors.mjs +91 -0
- package/firedrill/tools/netsuite/lib/handlers-meta.mjs +100 -0
- package/firedrill/tools/netsuite/lib/handlers-read.mjs +141 -0
- package/firedrill/tools/netsuite/lib/handlers-session.mjs +52 -0
- package/firedrill/tools/netsuite/lib/handlers-write.mjs +6 -0
- package/firedrill/tools/netsuite/lib/metadata.mjs +148 -0
- package/firedrill/tools/netsuite/lib/primitives.mjs +105 -0
- package/firedrill/tools/netsuite/lib/project-tran.mjs +153 -0
- package/firedrill/tools/netsuite/lib/project.mjs +178 -0
- package/firedrill/tools/netsuite/lib/q.mjs +280 -0
- package/firedrill/tools/netsuite/lib/session.mjs +188 -0
- package/firedrill/tools/netsuite/lib/suiteql-parse.mjs +382 -0
- package/firedrill/tools/netsuite/lib/suiteql-run.mjs +270 -0
- package/firedrill/tools/netsuite/lib/suiteql-select.mjs +118 -0
- package/firedrill/tools/netsuite/lib/suiteql-tables.mjs +162 -0
- package/firedrill/tools/netsuite/lib/tran.mjs +133 -0
- package/firedrill/tools/netsuite/lib/wire-decode.mjs +107 -0
- package/firedrill/tools/netsuite/lib/wire.mjs +228 -0
- package/firedrill/tools/netsuite/lib/write-customer.mjs +181 -0
- package/firedrill/tools/netsuite/lib/write-invoice.mjs +319 -0
- package/firedrill/tools/netsuite/lib/write-order.mjs +269 -0
- package/firedrill/tools/netsuite/netsuite.tool.json +4888 -0
- package/firedrill/transform-response-lost.scenario.json +11 -0
- package/firedrill/world.json +7711 -0
- package/firedrill/write-outage.scenario.json +11 -0
- package/firedrill.json +5 -0
- package/package.json +52 -0
- package/starter.json +6904 -0
- package/test/conformance.mjs +701 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 Reload Tech Inc.
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# @firedrill-tools/netsuite
|
|
2
|
+
|
|
3
|
+
A synthetic **NetSuite** Tool for [Firedrill](https://firedrill.run): one fictional two-subsidiary OneWorld account exposed through a
|
|
4
|
+
subset of **SuiteTalk REST Web Services** — the record service under `/services/rest/record/v1` and **SuiteQL** under
|
|
5
|
+
`/services/rest/query/v1/suiteql` — plus four operations shaped like Oracle's MCP Standard Tools. All state is local and
|
|
6
|
+
deterministic: Firedrill owns the ledger, virtual time and evidence. No Oracle service is contacted, no e-mail is delivered and no
|
|
7
|
+
money moves.
|
|
8
|
+
|
|
9
|
+
Version `0.1.0`, engine `>=0.1.0 <0.2.0`, licence Apache-2.0. The package ships a **browser app** as well as the backend: a
|
|
10
|
+
recreation of the NetSuite application shell (see *Browser app* below) that drives the same operations an agent calls.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
firedrill tool add @firedrill-tools/netsuite --install
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`firedrill tool add` seeds the starter account from `starter.json` (virtual time 2026-09-16T17:00:00Z, 09:00 account time at a fixed
|
|
19
|
+
−08:00). Grant the operations your agent needs, for example:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{ "packageId": "netsuite", "operationId": "customer.list" },
|
|
23
|
+
{ "packageId": "netsuite", "operationId": "sales-order.transform" },
|
|
24
|
+
{ "packageId": "netsuite", "operationId": "suiteql.query" }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Connecting a client
|
|
28
|
+
|
|
29
|
+
**HTTP.** Use `FIREDRILL_HTTP_URL` as the base URL instead of `https://<accountId>.suitetalk.api.netsuite.com` and
|
|
30
|
+
`FIREDRILL_HTTP_TOKEN` as the OAuth 2 Bearer token; the token's *value* is not inspected, the acting identity comes from the actor's
|
|
31
|
+
attributes. Paths keep NetSuite's shape, for example `GET /services/rest/record/v1/customer?q=…&limit=…&offset=…`,
|
|
32
|
+
`PATCH /services/rest/record/v1/invoice/{id}`, `POST /services/rest/record/v1/salesOrder/{id}/!transform/invoice` and
|
|
33
|
+
`POST /services/rest/query/v1/suiteql` (which requires `Prefer: transient`). Request headers decoded to arguments:
|
|
34
|
+
`Prefer`, `X-NetSuite-Idempotency-Key` (must be an RFC 4122 UUID), `X-NetSuite-PropertyNameValidation`
|
|
35
|
+
(`ignore|warning|error`, default `warning`) and `If-Match`. Successful writes answer **204** with a relative `Location` header.
|
|
36
|
+
|
|
37
|
+
**Canonical / MCP.** `record.get`, `record.metadata`, `suiteql.run` and `subsidiary.list` carry the argument shapes of the MCP
|
|
38
|
+
Standard Tools `ns_getRecord`, `ns_getRecordTypeMetadata`, `ns_runCustomSuiteQL` and `ns_getSubsidiaries`. `record.get` and
|
|
39
|
+
`suiteql.run` have **no** HTTP route — call them through the canonical operation endpoint.
|
|
40
|
+
|
|
41
|
+
## Identities
|
|
42
|
+
|
|
43
|
+
| actor attribute | meaning |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `netsuiteAccountId` (optional) | The account the token belongs to. Present and ≠ `TSTDRV2184096` → `INVALID_LOGIN` (401) on every operation. |
|
|
46
|
+
| `netsuiteRoleId` (optional) | The role in use. Absent → role `3` (Administrator). An unseeded role id → `INVALID_LOGIN`. |
|
|
47
|
+
| `netsuiteEmployeeId` (optional) | The logged-in employee; drives `createdBy`/`lastModifiedBy`. Absent → the account default `101`. Unknown → `INVALID_LOGIN`. |
|
|
48
|
+
| `netsuiteSubsidiaryId` (optional) | Overrides the role's subsidiary restriction. `"0"` means all subsidiaries. |
|
|
49
|
+
|
|
50
|
+
Rules apply in order: **account** → **permission** (NetSuite's `none < view < create < edit < full` over `LIST_CUSTJOB`,
|
|
51
|
+
`TRAN_SALESORD`, `TRAN_CUSTINVC`, `TRAN_CUSTPYMT`, `LIST_ITEM`, `LIST_SUBSIDIARY`, `SETUP_RECORD_METADATA`, `REPO_ANALYTICS`) →
|
|
52
|
+
**subsidiary scope**. An out-of-scope record answers `NONEXISTENT_ID` (404) rather than 403, as NetSuite hides it. There is no
|
|
53
|
+
per-row owner. **Fresh-install fallback:** an actor without attributes acts as employee 101 in the Administrator role.
|
|
54
|
+
|
|
55
|
+
Seeded roles: `3` Administrator, `1001` A/R Clerk (sales orders read-only, no delete), `1002` Sales Rep (no invoices, payments or
|
|
56
|
+
SuiteQL), `1003` Canada Controller (A/R Clerk permissions restricted to subsidiary 2), `1004` Restricted Viewer (no permissions).
|
|
57
|
+
|
|
58
|
+
## Starting data
|
|
59
|
+
|
|
60
|
+
**Whitlock Instruments Inc**, a fictional Seattle oceanographic-instrument maker with a Canadian subsidiary, 69 state rows:
|
|
61
|
+
2 subsidiaries, 3 employees, 5 roles, 12 customers (ids 1001–1012), 10 items (ids 2001–2010), 9 sales orders, 11 invoices and
|
|
62
|
+
5 customer payments (ids 5001–5055), plus `meta/account`, `meta/counters`, `meta/limits` and one `line-counters` row per transaction.
|
|
63
|
+
|
|
64
|
+
Deliberate edge cases: an inactive customer (1004) and an inactive item (2006); a customer with no e-mail (1003); an individual
|
|
65
|
+
rather than a company (1008); a customer whose `entityId` is at the 83-character limit (1011); two customers whose names differ only
|
|
66
|
+
in case (1002 / 1012); an item stocked only in subsidiary 2 (2005); three service items that are **not** in the `inventoryItem`
|
|
67
|
+
collection (2008–2010); a zero-total invoice (5039); a fractional quantity (order 5008); a 200-line order and its 200-line invoice
|
|
68
|
+
(5009 / 5040); a voided invoice (5036); a payment with 1,500.00 unapplied (5054); and a partially billed order (5002).
|
|
69
|
+
|
|
70
|
+
Internal ids are NetSuite-style numeric strings drawn from three counters (`nextEntityId`, `nextItemId`, `nextTransactionId`), and
|
|
71
|
+
the row id **is** the internal id. Money is stored with two decimals and computed in integer cents; quantities and rates carry up to
|
|
72
|
+
four decimals. Balances (`balance`, `overdueBalance`, `unbilledOrders`, `amountRemaining`) are **computed on every read** from the
|
|
73
|
+
transactions, never stored.
|
|
74
|
+
|
|
75
|
+
## Operations
|
|
76
|
+
|
|
77
|
+
26 operations over 24 provider-shaped routes: `list`/`get` for customer, sales order, invoice, customer payment and inventory item;
|
|
78
|
+
`create`/`update` for customer, sales order and invoice; `customer.delete`; `sales-order.items.list`; the two transforms
|
|
79
|
+
(`salesOrder → invoice`, `invoice → customerPayment`); `subsidiary.list`; `suiteql.query`; the four MCP-shaped operations; and `session.get`, which returns the signed-in session
|
|
80
|
+
(account, employee, role with its permission levels, subsidiary scope, server time and account date). `session.get` has **no HTTP
|
|
81
|
+
route** — NetSuite exposes this through its application shell, not as a REST resource — and is what the browser app reads to render
|
|
82
|
+
the account/role chrome and to date every screen from world virtual time.
|
|
83
|
+
Declared errors use NetSuite's problem envelope
|
|
84
|
+
(`{ type, title, status, "o:errorDetails": [ { detail, "o:errorCode", … } ] }`): `INVALID_LOGIN` 401, `INSUFFICIENT_PERMISSION` 403,
|
|
85
|
+
`NONEXISTENT_ID` 404, `RCRD_HAS_BEEN_CHANGED` 409, `CONCURRENCY_LIMIT_EXCEEDED` 429, `UNEXPECTED_ERROR` 500, and
|
|
86
|
+
`INVALID_ID` / `INVALID_CONTENT` / `INVALID_KEY_OR_REF` / `INVALID_PARAMETER` / `INVALID_REQUEST` / `USER_ERROR` /
|
|
87
|
+
`RESULT_SET_TOO_LARGE` at 400.
|
|
88
|
+
|
|
89
|
+
**`q` filters.** `customer.list`, `sales-order.list`, `invoice.list`, `customer-payment.list` and `inventory-item.list` accept
|
|
90
|
+
NetSuite's `q` grammar (`field OPERATOR value`, `AND` binding tighter than `OR`, parentheses, `[a,b]` lists) over a published field
|
|
91
|
+
list per record type. Operators follow the field's type: `EMPTY`/`EMPTY_NOT` anywhere, `IS`/`IS_NOT` for booleans and strings,
|
|
92
|
+
`CONTAIN`/`START_WITH`/`ENDWITH`, `ANY_OF`/`BETWEEN`/`EQUAL`/`GREATER…`/`LESS…`/`WITHIN` for numbers, `ON`/`BEFORE`/`AFTER`/
|
|
93
|
+
`ON_OR_BEFORE`/`ON_OR_AFTER` for dates, each with a `_NOT` form. An unknown field or an operator the type does not accept is
|
|
94
|
+
`INVALID_REQUEST`, never a silently ignored filter. Status is filtered by its **stored key** (`pendingFulfillment`, `paidInFull`, …),
|
|
95
|
+
not by NetSuite's `SalesOrd:B` form.
|
|
96
|
+
|
|
97
|
+
**Paging.** `limit` 1–1000 (default 1000; SuiteQL default 10) and an `offset` that must be a multiple of `limit`. Ordering is
|
|
98
|
+
internal id ascending, numerically — NetSuite does not document a default order, so this package fixes one. Pages are also bounded by
|
|
99
|
+
encoded UTF-8 bytes (`meta/limits.maxPageBytes`, default 900,000) and every scan is bounded by `meta/limits.maxScanRows`
|
|
100
|
+
(default 5,000); reaching either bound fails `RESULT_SET_TOO_LARGE` rather than silently truncating.
|
|
101
|
+
|
|
102
|
+
**SuiteQL subset.** `SELECT [DISTINCT] … FROM <table> [[INNER|LEFT] JOIN … ON …] [WHERE …] [GROUP BY …] [HAVING …] [ORDER BY …]
|
|
103
|
+
[FETCH FIRST n ROWS ONLY]` over `customer`, `transaction`, `transactionLine`, `item`, `employee` and `subsidiary`, with column and
|
|
104
|
+
table aliases, `?` bind parameters, `COUNT`/`SUM`/`MIN`/`MAX`/`AVG`, `UPPER`/`LOWER`/`NVL`, `LIKE`, `IN`, `BETWEEN`, `IS [NOT] NULL`
|
|
105
|
+
and `BUILTIN.DF(<column>)`. Every scalar is rendered as a JSON string, as NetSuite does. A join condition must be one or more column
|
|
106
|
+
equalities so the joined table is indexed into a `Map` once per request. Anything outside the subset — a non-`SELECT` statement, a
|
|
107
|
+
second statement, subqueries, `UNION`, `CASE`, window functions, an unknown table or column — fails `INVALID_REQUEST` naming the
|
|
108
|
+
construct. `Prefer: transient` is required on the HTTP route.
|
|
109
|
+
|
|
110
|
+
## Events and faults
|
|
111
|
+
|
|
112
|
+
Events: `record.changed` (`{ recordType, recordId, changeType: CREATE|UPDATE|DELETE, occurredAt, employeeId, subsidiaryId }`) and
|
|
113
|
+
`transaction.status-changed` (`{ recordType, recordId, tranId, previousStatus, status, occurredAt, total, amountRemaining }`).
|
|
114
|
+
Nothing is delivered anywhere; there are no subscriptions and no callbacks (NetSuite has no first-party outbound webhook for record
|
|
115
|
+
changes, so declaring one would invent a protocol).
|
|
116
|
+
|
|
117
|
+
Faults, each with a scenario in `firedrill/`: `concurrency-limit` (every operation → 429, `Retry-After: 5`), `write-outage` (the nine
|
|
118
|
+
write operations → 500, reads keep working), `record-locked` (the five edit/transform operations → 409) and
|
|
119
|
+
`transform-response-lost` (`after_commit` on the two transforms: the invoice or payment **is** created and its events emitted, but
|
|
120
|
+
the caller sees 500 — the double-billing hazard worth rehearsing).
|
|
121
|
+
|
|
122
|
+
## Conformance
|
|
123
|
+
|
|
124
|
+
`firedrill tool test netsuite` runs 15 drills over 6 scenarios and 7 actors and passes with **zero coverage violations**: every one
|
|
125
|
+
of the 25 operations has a successful call, every declared error of every operation is observed, both events are emitted and all four
|
|
126
|
+
faults activate, over two identical runs. See [`specs/netsuite/VERIFICATION.md`](../../specs/netsuite/VERIFICATION.md) in this
|
|
127
|
+
repository for the exact commands and outputs.
|
|
128
|
+
|
|
129
|
+
## Browser app
|
|
130
|
+
|
|
131
|
+
`firedrill serve` publishes a browser app for this Tool (**Tools → Open app**). It is a recreation of the NetSuite
|
|
132
|
+
application shell — the dark application bar with the Oracle NetSuite lockup, global search, Recent Records, Create New,
|
|
133
|
+
Shortcuts, Help and the user menu; the blue global menu bar (Activities, Payments, Transactions, Lists, Reports,
|
|
134
|
+
Analytics, Documents, Setup, Customization, Commerce, Support) with their real submenus; the account/role strip; and the
|
|
135
|
+
release footer. Screens:
|
|
136
|
+
|
|
137
|
+
| screen | route | operations |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| Home dashboard — the full portlet grid: Reminders (seven counters), Recent Records, Navigation, Tasks, Key Performance Indicators, Trend Graphs (invoiced sales by month), Report Snapshots (Sales by Customer), Settings, Shortcuts, Custom Search (Open Invoices), Calendar (month grid on the account date), Phone Calls, Tips. Every figure is paged out of the Tool's own collections; Tasks, Phone Calls and calendar events are rendered empty and labelled not simulated | `#/home` | `invoice.list`/`get`, `sales-order.list`/`get`, `customer-payment.list`, `session.get` |
|
|
140
|
+
| Customers list — quick filter mapped to a real `q`, Show Inactives, NetSuite's paging strip | `#/customers` | `customer.list`, `customer.get` |
|
|
141
|
+
| Customer record — Primary Information / Address / Financial / Transactions / System Information subtabs, Edit form, Actions → Delete with confirmation | `#/customers/<id>` | `customer.get`, `customer.update`, `customer.delete`, `sales-order.list`, `invoice.list`, `subsidiary.list` |
|
|
142
|
+
| Sales Orders list with a status filter | `#/orders` | `sales-order.list`, `sales-order.get` |
|
|
143
|
+
| Sales order record — Items sublist with its own paging, totals, **Bill** (whole order or a quantity per line) | `#/orders/<id>` | `sales-order.get`, `sales-order.items.list`, `sales-order.transform` |
|
|
144
|
+
| Enter Sales Order — customer and item pickers that page as you type, line grid, live subtotal | `#/orders/new` | `customer.list`/`get`, `inventory-item.list`/`get`, `sales-order.create` |
|
|
145
|
+
| Invoices list — Open / Overdue / Paid In Full / All views | `#/invoices` | `invoice.list`, `invoice.get` |
|
|
146
|
+
| Invoice record — items, totals, applied payments, **Accept Payment** | `#/invoices/<id>` | `invoice.get`, `invoice.update`, `invoice.transform`, `customer-payment.list`/`get` |
|
|
147
|
+
| Create Invoice | `#/invoices/new` | `invoice.create`, `customer.list`, `inventory-item.list` |
|
|
148
|
+
| Customer Payments list | `#/payments` | `customer-payment.list`, `customer-payment.get` |
|
|
149
|
+
| Items (read-only) with a detail panel | `#/items` | `inventory-item.list`, `inventory-item.get` |
|
|
150
|
+
| Subsidiaries (read-only) | `#/subsidiaries` | `subsidiary.list` |
|
|
151
|
+
| SuiteQL Query Tool — editor, results grid with paging, raw response body | `#/suiteql` | `suiteql.query` |
|
|
152
|
+
|
|
153
|
+
The app and the API share one world: a customer created in the app is visible over `GET /services/rest/record/v1/customer`
|
|
154
|
+
and the reverse, and the app re-reads after every write and whenever `getContext().revision` moves.
|
|
155
|
+
|
|
156
|
+
Permissions and subsidiary scope come from the acting role through `session.get`: an action the role cannot perform is
|
|
157
|
+
disabled, and a refused read renders NetSuite's own "Permission Violation" text. "Today", due-date colouring and the
|
|
158
|
+
account date come from world virtual time (`session.get.accountDate`), never from the browser clock, so replaying a world
|
|
159
|
+
on another day renders identically.
|
|
160
|
+
|
|
161
|
+
**Controls that are present but not simulated.** Every persistent control of the real client is rendered with its label and
|
|
162
|
+
hover state; those outside this Tool's scope open a short "Not simulated by this Tool" panel: global search suggestions
|
|
163
|
+
(the search box itself filters customers), Help, Shortcuts personalization, the user menu (Set Preferences, Change
|
|
164
|
+
Password, Change Role; Log Out is disabled), the role switcher, portlet setup and Personalize Dashboard, the Tasks / Phone Calls / Calendar portlets (their New and View All buttons; activities are not modelled, so the lists are empty rather than invented), the Navigation portlet's out-of-scope menu entries, Customize
|
|
165
|
+
View / Export on every list, Print / Email / More and the unsupported entries of each record's Actions menu (Fulfill,
|
|
166
|
+
Close Order, Void, Credit Memo, Make Copy, Merge, Make Inactive), New Item, New Subsidiary, Save & New / Save & Print /
|
|
167
|
+
Reset on the entry forms, Save Query / Query History in the SuiteQL tool, and every global-menu entry outside the table
|
|
168
|
+
above (Activities, Reports, Documents, Customization, Commerce, Support and the unimplemented Transactions, Lists,
|
|
169
|
+
Payments and Setup entries). Nothing invents data for them.
|
|
170
|
+
|
|
171
|
+
Assets: `app/assets/oracle-netsuite.svg` and `app/assets/oracle.svg` are the official downloaded Oracle marks (sources and
|
|
172
|
+
checksums in `app/assets/ATTRIBUTION.md`); the bundled face is Open Sans (SIL OFL 1.1, `app/assets/fonts/LICENSE.txt`),
|
|
173
|
+
since NetSuite's own Oracle Sans is proprietary. Interface glyphs are original SVG paths in the app's `icons.js`.
|
|
174
|
+
|
|
175
|
+
## Trademarks
|
|
176
|
+
|
|
177
|
+
Oracle, NetSuite, Oracle NetSuite, SuiteTalk, SuiteQL, SuiteAnalytics and the Oracle NetSuite logo are trademarks of
|
|
178
|
+
Oracle Corporation. This package is an independent, unofficial simulation: product names and logos are used only to
|
|
179
|
+
identify the simulated service inside a Firedrill test environment. It is not affiliated with, endorsed by, sponsored by
|
|
180
|
+
or connected to Oracle Corporation, and it contacts no Oracle service.
|
|
181
|
+
|
|
182
|
+
## Limitations
|
|
183
|
+
|
|
184
|
+
- **No official client has been run against this package.** Fidelity is `behavioral` / `stateful` / `contract`; nothing claims
|
|
185
|
+
`validated`. Oracle's SuiteCloud SDK and the MCP Standard Tools SuiteApp were used as documentation, not as test clients.
|
|
186
|
+
- The account time zone is a fixed −08:00 with no daylight saving, and the fiscal calendar is not modelled.
|
|
187
|
+
- Currency is per subsidiary (USD / CAD) with **no exchange rates and no consolidation**; a CAD transaction's total is a CAD number.
|
|
188
|
+
- Taxes are a flat per-subsidiary rate (8.6 % / 5 %) on each line, not a tax-code engine; there are no tax groups, no nexuses and no
|
|
189
|
+
tax items.
|
|
190
|
+
- No fulfillments, item receipts, credit memos, journal entries, deposits, custom records, custom fields, saved searches, workflows,
|
|
191
|
+
SuiteScript, file cabinet, employees-as-vendors, or sublists other than `item`, `apply`, `addressBook` and `subsidiary`.
|
|
192
|
+
- Sales-order status is derived from billed quantities only (approval and fulfilment are not simulated), so `partiallyFulfilled` and
|
|
193
|
+
`pendingBilling` appear in starter data but are never *computed*.
|
|
194
|
+
- `subsidiary`, `employee` and `role` rows are read-only; there is no operation that writes them.
|
|
195
|
+
- The metadata catalog is assembled on every call from a field table in `lib/metadata.mjs` that mirrors the state schemas and the `q`
|
|
196
|
+
filter tables. It is not the real NetSuite `application/schema+json` document, and its `x-ns-*` keys are this package's own.
|
|
197
|
+
- Two request shapes are refused by the framework before this Tool's codecs see them, so they answer Firedrill's envelope rather than
|
|
198
|
+
NetSuite's: a body that is not JSON at all, and a body over the framework's size limit. A `__proto__` key is also removed by the
|
|
199
|
+
framework's JSON decoding before the codec runs; a `constructor` or `prototype` key does reach the codec and answers
|
|
200
|
+
`INVALID_REQUEST`.
|
|
201
|
+
- When the *world* denies an operation (an actor whose grants do not cover it), the denial is raised by Firedrill before any handler
|
|
202
|
+
runs. The provider-shaped routes still answer a coherent NetSuite envelope — `403` with `INSUFFICIENT_PERMISSION` and a generic
|
|
203
|
+
"Permission Violation: ..." detail — because the codec maps a denied outcome itself and never quotes the framework's own
|
|
204
|
+
message (which names the internal operation id). The detail is therefore less specific than a role-permission refusal, which names
|
|
205
|
+
the missing permission. On the canonical operation endpoint the denial keeps Firedrill's `world.OPERATION_DENIED` outcome, as it must.
|
|
206
|
+
- An argument that violates an operation's own input schema (a value longer or larger than the schema allows) is rejected by
|
|
207
|
+
Firedrill before the handler runs. The routes still answer a coherent NetSuite envelope — `400` with `INVALID_PARAMETER` and a
|
|
208
|
+
generic "Invalid request parameter value. ..." detail — because the codec derives the NetSuite code from the outcome status the
|
|
209
|
+
framework reports, so the body's `status` always matches the HTTP status line. The bounds this package documents (`fields` at most
|
|
210
|
+
40 names, `q` at most 2000 characters, per-field lengths on write bodies) are checked in the handlers, so those cases answer the
|
|
211
|
+
specific `INVALID_PARAMETER` / `INVALID_REQUEST` / `INVALID_CONTENT` detail with `o:errorQueryParam` or `o:errorPath`.
|
|
212
|
+
- `Location` headers and every `links[].href` are **relative** paths: a codec cannot see the request host.
|
|
213
|
+
- Idempotency is the framework's: a retry carrying the same `X-NetSuite-Idempotency-Key` replays the recorded outcome. NetSuite's own
|
|
214
|
+
header behaviour (and its 4-hour window) is not otherwise simulated.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"target": {
|
|
4
|
+
"id": "conformance-agent",
|
|
5
|
+
"kind": "command",
|
|
6
|
+
"bindings": [
|
|
7
|
+
"http"
|
|
8
|
+
],
|
|
9
|
+
"executable": "node",
|
|
10
|
+
"arguments": [
|
|
11
|
+
"test/conformance.mjs"
|
|
12
|
+
],
|
|
13
|
+
"workingDirectory": ".",
|
|
14
|
+
"timeoutMs": 10000
|
|
15
|
+
}
|
|
16
|
+
}
|