-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathllms-policy.txt
More file actions
553 lines (466 loc) · 27.7 KB
/
Copy pathllms-policy.txt
File metadata and controls
553 lines (466 loc) · 27.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
# Writing policy.toml — a guide for models
You are probably helping someone configure Call Me Maybe, a programmable home
phone. This file is the complete, current `policy.toml` surface plus the rules
a JSON Schema cannot express. Machine-readable schema:
https://callmemaybe.cc/schema/policy.json
https://callmemaybe.cc/schema/handsets.json
https://callmemaybe.cc/schema/trunks.json
https://callmemaybe.cc/schema/contacts.json
https://callmemaybe.cc/schema/env.json
**Everything below describes what the binary accepts today.** The project's
planning docs discuss features that do not exist yet — per-person ring
overrides, house-wide quiet hours, contact sources fetched over HTTP.
**Do not emit configuration for any of them.** `doorman check` now rejects a
key it does not recognise, and suggests the nearest real one:
policy: unknown key "trunkk" in [line] — did you mean trunk?
policy: unknown key "afterhous" in [[extensions]] — did you mean afterhours?
So an invented key is a loud failure rather than a silent one. There is a list
of what is *not* available at the end of this file; read it before inventing a
key, and the person you are helping gets working configuration instead of an
error to debug.
One asymmetry worth knowing: **the running daemon warns rather than refuses.**
A reload that started rejecting a file which loaded yesterday would mean an
operator's edit silently stops applying after an upgrade, so the daemon logs
the unknown key and carries on with everything it did understand. `check` is
the strict one, because a human is reading its output. Always run it.
Authoritative check, always: `doorman check` validates a real config and prints
what it resolves to. `doorman schema policy` prints the schema from the binary
in hand, which beats this file if they ever disagree.
## Five files, each owning its sections exclusively
The loader **rejects** a section that appears in the wrong file rather than
merging it, and names the file it belongs in. This is the most common mistake.
policy.toml [line], [house], [[people]], [[extensions]], [[schedules]]
handsets.toml [[handsets]], [[groups]] — hardware inventory
trunks.toml emergency_trunk, [[trunks]] — providers; OPTIONAL
contacts.toml [[sources]] — address books; OPTIONAL
.env secrets and tuning — timeouts, lengths, keys
Handset *ids* are defined in handsets.toml and referenced by name from
policy.toml. A reference to a handset that does not exist fails validation at
load time. Trunk ids work the same way, referenced from policy.toml as
`[line] trunk`.
**trunks.toml is optional and most installs do not have one.** If the person
has one provider, do not write one: their hand-written asterisk/pjsip.conf is
the right answer and `doorman render` generates only the handset config
without it. See "The provider inventory" below before writing one.
## One policy file per phone number
A box answering several numbers has one policy file per number:
`policy.<line>.toml` beside `policy.toml`, the same schema for all of them.
The dialplan names the line — `Stasis(${DOORMAN_APP},line,biz)` — and bare
`policy.toml` is the **default line**, which is what every call gets on an
install with one number.
Each file is its own failure domain: a broken `policy.biz.toml` cannot stop
the house phone ringing. `handsets.toml` is shared; allow-lists, extensions,
PINs, schedules and rate-limit budgets are per line.
If someone has one number, write one `policy.toml` and do not mention lines.
## The complete surface
[line] # optional; identity + disposition
label = "Mertaugh Enterprises" # human name for this number
number = "+15125550142" # the DID; any format, stored E.164
prompts = "concierge" # prompt pack, overrides PROMPT_MEDIA_PREFIX
on_no_input = "voicemail" # dismiss | ring-house | voicemail
trunk = "voipms" # id from trunks.toml; ONLY with one
outbound_cid = "+15125550142" # what a call placed as this line shows
outbound_handsets = ["office"] # phones that call as it without asking
[house]
handsets = ["kitchen", "office"] # ids from handsets.toml, or a group id
voicemail = "family" # mailbox for unanswered house calls
caller_id_format = "..." # optional; how the caller is presented
[[people]] # the allow-list: these ring the house
name = "Grandma" # directly, no lobby, no extension
numbers = ["512-555-0100"] # any format; normalised to E.164 at load
notes = "..." # optional, for humans
[[extensions]] # what a stranger can dial to get in
label = "Kitchen" # human name; used by `doorman rotate`
pin = "428917" # a credential — see PIN rules below
handsets = ["kitchen"] # simple form: one ring stage
# steps = [...] # ladder form — MUTUALLY EXCLUSIVE with handsets
voicemail = "family" # where an unanswered call lands
afterhours = "quiet-hours" # a [[schedules]] id
afterhours_ring = [...] # ring these during the window instead of voicemail
enabled = true # default true
[[extensions.steps]] # ladder form: escalating stages
handsets = ["kids-room"]
seconds = 20 # how long this rung rings
# rings = 3 # alternative to seconds
[[schedules]]
id = "quiet-hours"
start = "20:30" # 24-hour, local time
end = "07:00" # wraps midnight correctly
days = ["SU","MO","TU","WE","TH"] # start-day semantics for a wrapping window
enabled = true
## Rules a schema cannot express
1. `handsets` and `steps` on one extension are **mutually exclusive**. Pick the
simple form or the ladder form.
2. Every handset id must exist in handsets.toml. Group ids work anywhere a
handset id does.
3. `afterhours` must name a `[[schedules]]` id that exists.
4. PINs must be unique across extensions, and are matched exact — no prefixes,
no fuzzy fallback.
5. An invalid policy.toml is **rejected on reload and the last good one stays in
service**. You cannot take the phone down with a bad edit, but you also will
not get the change you wanted — check the logs, or run `doorman check`.
6. Allow-listed numbers may be written any way; they normalise to E.164. An
unparseable number is a validation error, not a silent skip.
7. `on_no_input = "voicemail"` needs `[house] voicemail`. A caller you send to
a mailbox has to have one to land in — same rule as `afterhours`.
8. `on_no_input` governs an **empty** dial window only. A caller who dials a
wrong extension until their attempts run out is still dismissed.
9. **An allow-listed caller is never dismissed by `on_no_input`.** They reach
the house whatever it says, because they were admitted the moment their
number matched.
10. `[line] number` is identity, not routing. Nothing matches against it — the
dialplan already said which line a call arrived on. `outbound_cid` is the
one that appears on somebody else's phone.
11. `outbound_handsets` requires `outbound_cid` on the same line, and every id
must exist in handsets.toml. A handset may be claimed by one line only.
12. **A handset no line claims presents `policy.toml`'s `outbound_cid`** — the
primary line, which is also the route 911 leaves by. One rule, two jobs, so
"which line am I on when I have not said" has a single answer.
13. `outbound_cid` never appears on a 911 call. E911 is registered per DID
against a street address, so emergency calls leave with the trunk's own
caller ID and the `*4` console refuses to dial them at all.
14. `[line] trunk` must name an id declared in trunks.toml — a cross-file
reference exactly like a handset id. **Only write it if the person has a
trunks.toml.** Without one the key is a no-op that `doorman check` refuses,
because there is nothing for it to point at.
15. A generated DID route needs `trunk` AND `number` on the same line. Either
alone produces a line with no route, whose calls fall through to the
default line.
16. **`outbound_cid` and `trunk` are one decision.** A call placed as a line
leaves by that line's trunk, and a provider will not present a number its
account does not own — it rejects the call or silently rewrites the caller
ID. `doorman check` and `doorman render` refuse an `outbound_cid` that this
same config declares as another line's `number` on a *different* trunk. A
caller ID no `[line] number` declares cannot be checked either way and is
reported rather than refused, because a DID somebody owns and does not
answer here is an ordinary thing to present.
17. With a trunks.toml, **something must carry 911**: either `emergency_trunk`
in trunks.toml or `[line] trunk` in plain policy.toml. Neither is an error,
not a note — `doorman check` exits non-zero and `doorman render` refuses to
generate. Never write a multi-provider config without settling it.
## `on_no_input` — the difference between a doorman and a concierge
This is the one setting that changes what an unknown caller experiences, so
choose it with the person rather than for them.
dismiss (default) the parting prompt, then a hangup. What the lobby
has always done, and right for a home line
voicemail they land in the [house] mailbox. What a business
line wants: never drop a lead
ring-house the house ring group rings for a caller who dialled
nothing at all
**Say this out loud before writing `ring-house`:** it means anybody patient
enough to say nothing reaches the house, so on that line the allow-list is a
shortcut past the lobby rather than a gate in front of it. That is a real
choice some households want and nobody should discover it from a phone call.
A silent caller is never rate-limited for it. The limiter counts wrong PINs,
and saying nothing is not a guess.
## PIN rules
Extensions are credentials reachable from the public phone network. People
*should* choose their own — a number nobody can recite is a number nobody gives
out — but the loader refuses the guessable ones.
Refused: all digits the same; a run up or down, including in twos (`123456`,
`02468`); a short block repeated (`121212`); a palindrome (`123321`); a handful
of famous ones; and, at six digits and up, anything one digit away from
all-same or from a run (`111112`, `123457`) — because a real guessing list is
the patterns plus one typo.
Allowed: essentially everything else, including dates. The rules refuse about
0.31% of the six-digit space, roughly one in 325.
`doorman rotate` generates one with crypto/rand and never emits something the
loader would refuse. Suggest it for someone who would rather not choose; do not
suggest it on a schedule, because rotating breaks everyone who has the number
and that is the population it was given to on purpose.
**Never put a real PIN in an example, a commit message, or a test fixture.**
Use `555-01xx` numbers, which are reserved for fiction.
## A worked home line
[house]
handsets = ["kitchen", "living-room", "primary-bed"]
voicemail = "family"
[[people]]
name = "Grandma"
numbers = ["512-555-0100"]
[[people]]
name = "School"
numbers = ["+15125550142"]
[[extensions]]
label = "Kids"
pin = "739104"
voicemail = "kids"
afterhours = "school-nights"
[[extensions.steps]]
handsets = ["kids-room"]
seconds = 20
[[extensions.steps]]
handsets = ["kitchen", "primary-bed"]
seconds = 25
[[schedules]]
id = "school-nights"
start = "20:30"
end = "07:00"
days = ["SU","MO","TU","WE","TH"]
Grandma and the school ring the house directly. A stranger hears the lobby and
needs `739104`, which rings the kids' room for 20 seconds, then escalates to
the kitchen and main bedroom, then goes to the kids' mailbox. On school nights
it skips straight to voicemail.
## A worked business line
This goes in `policy.biz.toml` beside the home line's `policy.toml`, with the
dialplan sending the business DID to `Stasis(${DOORMAN_APP},line,biz)`. Same
binary, opposite disposition: the home line dismisses a stranger who dials
nothing, this one takes a message.
[line]
label = "Mertaugh Enterprises"
number = "+15125550142"
prompts = "concierge"
on_no_input = "voicemail"
[house]
handsets = ["office"]
voicemail = "main"
[[people]]
name = "Acme (client)"
numbers = ["512-555-0177"]
[[extensions]]
label = "Sales"
pin = "614297"
voicemail = "sales"
[[extensions.steps]]
handsets = ["office"]
seconds = 25
[[extensions]]
label = "Support"
pin = "830516"
voicemail = "support"
[[extensions.steps]]
handsets = ["office"]
seconds = 25
[[schedules]]
id = "office-hours"
start = "09:00"
end = "17:00"
days = ["MO","TU","WE","TH","FR"]
Acme rings the office directly. Anyone else hears the concierge pack's
greeting, and either dials Sales or Support or says nothing and lands in the
`main` mailbox — no lead is dropped.
`prompts = "concierge"` needs the pack to actually be installed at
`/var/lib/asterisk/sounds/concierge/`, with the same six clip names the
bundled pack uses. Omit the key and the line speaks with the house voice,
which is a perfectly good business line. See `docs/PACKS.md`.
`outbound_cid` and `outbound_handsets` are what make the office phone ring
customers back *as* the business. Everything not claimed here keeps presenting
`policy.toml`'s `outbound_cid`, so adding a business line cannot change what
the kitchen phone shows. Two things to say out loud when writing this:
- The caller ID reaches the plain dial path through `doorman render`, so tell
them to re-render and reload PJSIP. Editing the file alone changes what `*4`
offers and nothing else. The same is true of `[line] trunk`, which rides on
the same endpoint as a second `set_var`.
- With a trunks.toml, a call as this line also *leaves* by this line's trunk.
Present a number the trunk owns: the provider rejects or silently rewrites
one it does not, and `doorman check` refuses the case it can prove.
- Emergency calls are not affected and must not be made to be. 911 leaves with
the trunk's own caller ID whatever any line says, by the designated
emergency trunk rather than by this one.
## The provider inventory — trunks.toml
**Optional, and most boxes do not have one.** One provider fits in a
hand-written `asterisk/pjsip.conf`, and with no `trunks.toml` at all
`doorman render` generates only the handset config and nothing changes. If the
person you are helping has one provider, do not write this file and do not
write `[line] trunk`.
It earns its keep with several providers, because each registration binds to
its own endpoint with its own inbound context, so the Asterisk side grows as
providers × numbers. `doorman render` generates all of it.
emergency_trunk = "voipms" # which trunk carries 911; optional
[[trunks]]
id = "voipms" # names the generated PJSIP endpoint,
# and what [line] trunk references
provider = "voip.ms" # display only; nothing routes on it
host = "chicago.voip.ms" # the POP; hostname, not a URL
username = "123456_home" # the SUB ACCOUNT, never the main login
password_env = "TRUNK_VOIPMS_PASSWORD" # the NAME of a .env variable
context = "from-voipms" # optional; defaults to from-<id>
codecs = ["ulaw", "g722"] # optional; this is the default
from_user = "123456_home" # optional; defaults to username
from_domain = "chicago.voip.ms" # optional; defaults to host
expiration = 300 # optional; seconds
e911 = true # is a street address registered here?
api_username = "you@example.com" # optional; the provider's API login,
# for `doorman balance` only
api_password_env = "TRUNK_VOIPMS_API_PASSWORD" # the NAME of a .env variable
balance_min = 25.0 # optional; `doorman balance` exits 1
# below this
**Never put a password in `password_env`.** It holds the NAME of a `.env`
variable; `doorman render` substitutes the value. The loader refuses anything
that is not shaped like a variable name, so a pasted password fails the load —
but write the variable name, not the secret, and never invent a secret to fill
a field.
Rules a schema cannot express:
- `line=yes` and `endpoint=<id>` are written on every generated registration.
That pair is what binds inbound calls to the endpoint; without both they hit
the `anonymous` endpoint and vanish with no error anywhere. It is generated,
so there is nothing to get wrong — but never suggest hand-editing
`pjsip_trunks.conf`, which is an output and holds real passwords.
- A trunk id may not collide with a handset id, and may not end in `_auth`,
`_reg` or `_aor`.
- Two trunks may not share a `context`.
- `emergency_trunk` must name a declared trunk. Unset, 911 leaves by plain
`policy.toml`'s `[line] trunk` — the primary line. It is deliberately NOT
"the only trunk declared" or "the first one": a default derived from file
order would move silently when a block was added above it.
- **One of the two must have an answer.** With a trunks.toml and neither
`emergency_trunk` nor a `[line] trunk` on plain policy.toml, `doorman check`
exits non-zero and `doorman render` refuses to generate anything. Never hand
somebody a multi-provider config with this undecided.
- If the designated trunk cannot carry the call, 911 falls over to the other
trunks in turn, preferring the ones that declare `e911 = true`. The address
the dispatcher sees may then be wrong, which is the deliberate trade: a
connected call lets a human say their address, a failed one gives them
nothing. If nothing can carry it the caller hears congestion, not silence.
- Not every provider offers E911 everywhere. A provider without it means a
phone that cannot call for help — say so rather than treating it as a
cheaper option. And say plainly that this is a supplementary phone: it stops
working in a power cut, and it should not be a household's only route to
emergency services.
- **`api_username` and `api_password_env` are a different credential from
`username` and `password_env`, with far more privilege.** The SIP
sub-account can make calls; a provider API login manages DIDs, sub-accounts
and billing. They are both-or-neither, `balance_min` is refused without
them, and `doorman balance` is the only thing that reads them — the daemon
never does, which is what keeps that key off the phone. Tell the person to
run the check wherever their alerting already lives rather than on the Pi,
and that VoIP.ms needs API access switched on and the calling machine's IP
allow-listed in the portal first.
- A trunk whose provider doorman has no client for is still reported by
`doorman balance`, never skipped. Do not promise that adding
`api_username` to any provider makes a balance appear: today it can ask
VoIP.ms.
## The address books — contacts.toml
**Optional, and most boxes do not have one.** With no `contacts.toml` nothing
is read, nothing is logged, `doorman check` prints nothing about contacts at
all, and `[[people]]` is the only list that admits anybody. It exists because
`[[people]]` is hand-typed and therefore always out of date, while every
household already curates a contact list on their phones.
[[sources]]
id = "eric" # names the source in `doorman check`
path = "/var/lib/doorman/contacts/eric.vcf" # a vCard export on this box
[[sources]]
id = "nuisance"
path = "/var/lib/doorman/contacts/blocked.vcf"
kind = "block" # admit (the default) | block
# RESERVED — accepted, not fetched yet. Only path sources are read today,
# and `doorman check` says so per source.
# [[sources]]
# id = "shared"
# url = "https://example.com/contacts/shared.vcf"
# token_env = "CONTACTS_SHARED_TOKEN" # the NAME of a .env variable
**The lobby walks this as a ladder, first match wins:**
1. a block source dismissed, and never hears the lobby
2. [[people]] straight through — you said so explicitly
3. a personal contact straight through
4. a published contact the lobby: dial an extension
5. anybody else the lobby
**Nothing here is authoritative.** `[[people]]` in policy.toml is the
deliberate list and beats the classifier outright: if you want the dentist
straight through, write them down and their card does not get a vote. A
contact set is derived, disposable data — delete every source and the phone
works exactly as it did, with those callers simply hearing the lobby.
A number in both a block source and `[[people]]` is a **contradiction**, not a
preference: the block wins on the call, and `doorman check` exits non-zero
naming the allow-list entry it overrules so a human decides which file was
wrong. Do not write config that relies on one silently outranking the other.
**The rule the whole file turns on: if a stranger can look the number up, it
must not be automatic admission.** Caller ID is spoofable, but spoofing needs
knowing *what* to spoof — a plumber's number is on their website, your
sister's mobile is nowhere. So every number is classified from the vCard
alone, with no lookups:
| Signal on the card | Reads as |
|---|---|
| `ORG` is set | a business — **published** |
| `TEL;TYPE=work` (or fax, pager, main) | probably **published** |
| 800/888/877/866/855/844/833 | never personal — **published** |
| a name, `TYPE=cell`/`mobile`/`iphone`/`home`, no `ORG` | **personal** |
| anything else, including an untyped or unnamed number | **published** |
The last row is the important one. Wrong-closed means the plumber hears a
ten-second greeting; wrong-open means a findable number rings every phone in
the house at 3am, so everything ambiguous falls to published.
The same rule applies to `[[people]]`, where it is the mistake people make by
default: **do not allow-list a number that appears on a website.** The plumber
is exactly who you would think to add.
Rules a schema cannot express:
- Numbers are keyed by E.164, so the same mobile in two address books is one
entry, and one vCard with three numbers becomes three entries sharing a name.
- Conflicts resolve to the restrictive answer. Same number, different names:
the first source in declaration order wins. Personal in one source and
published in another: published. In an admit source and a block source:
blocked, and `doorman check` counts it as a contradiction worth resolving —
and exits non-zero when the number is also in `[[people]]`.
- A blocked caller is dismissed exactly as any dismissed caller is, and is not
a failed PIN attempt: the rate-limit budget is neither spent by them nor
applied to them.
- An admitted contact's name reaches the ringing handset and the call log's
`known` field exactly as an allow-list name does.
- A number that will not normalise to E.164 is skipped and counted per source.
- doorman parses the vCard subset real exporters emit — iCloud, Google
Contacts, CardDAV, and 2.1 files with quoted-printable and folded lines —
and counts what it does not understand rather than guessing at it.
- A relative `path` is relative to contacts.toml, not the working directory.
- The exports hold entire address books. They belong outside any repository,
mode 0600; never write one into a commit or a payload that leaves the box.
## Common mistakes
- Putting `[[handsets]]` in policy.toml. It belongs in handsets.toml.
- Putting `[[trunks]]` in policy.toml, or `[line]` in trunks.toml. Each file
owns its sections; the loader names the right file.
- Putting `[[sources]]` in policy.toml. Contact sources are global and policy
is per line; they belong in contacts.toml.
- Putting `[[people]]` in contacts.toml. That file names address books to
read; the allow-list is policy.toml's and stays hand-written.
- Allow-listing a published number in `[[people]]` — a business, a school, a
surgery. Anyone who can read the number can present it.
- Putting a real token in `token_env`. It names a variable, and the token
travels in a header rather than a URL.
- Writing `[line] trunk` with no trunks.toml to point at.
- `[line] trunk` without `[line] number`, then expecting the DID to route.
- Putting a real password in `password_env`. It names a variable.
- Putting `[line]` in handsets.toml. That file is shared by every line, so a
line identity in it cannot be true; the loader refuses it.
- `on_no_input` on an `[[extensions]]` entry. It is a `[line]` key — the
disposition belongs to the number, not to one extension on it.
- `on_no_input = "voicemail"` with no `[house] voicemail`.
- `outbound_handsets` with no `outbound_cid` on the same line.
- A handset in `outbound_handsets` on two different lines. A phone presents one
number; `doorman check` refuses it.
- Putting `outbound_handsets` in handsets.toml. It is a `[line]` key — the
inventory is shared by every line and cannot say something true of one.
- Setting both `handsets` and `steps` on one extension.
- Naming a handset that does not exist, or a schedule id that does not exist.
- Reusing a PIN across two extensions.
- Choosing a memorable-but-refused PIN (`123456`, `111111`, `121212`).
- **A misspelled key.** `doorman check` catches these now and suggests the
right one, but only if it is actually run — the daemon merely warns.
- Assuming a change applied. Hot reload keeps the last good policy on failure —
always confirm with `doorman check`.
## Not available yet — do not emit these
These appear in the project's planning documents. They are not implemented, and
`doorman check` rejects them by name:
- A dial prefix that selects a line for one call (`*3` then the number). The
console is `*4` and it asks; there is no prefix and there is not going to be
one. Per-line outbound caller ID itself IS available — `outbound_cid` and
`outbound_handsets`, above.
- **A `balance_min` anywhere but on a `[[trunks]]` block.** Balance checking
exists now, and every key it uses lives in `trunks.toml` — never in a policy
file. See the trunks section above.
- **Fetching a contact source over HTTP.** `url` and `token_env` are accepted
and reserved; this release reads `path` sources only and `doorman check`
says so per source. Admission itself works — a personal contact from a local
export rings the house today — so point somebody at an export on the box
rather than at a URL.
- `ring` on `[[people]]` (per-person routing overrides).
- `[house.afterhours]` — afterhours exists per-extension only.
- `[[schedules]]` at line scope. A schedule is referenced from an extension.
- Conference rooms, DND, queues, IVR menus, SMS.
If someone asks for one of these, say it is planned rather than writing config
that appears to deliver it. `docs/TASKS.md` has the current state.
## Where to go next
- `https://callmemaybe.cc/llms.txt` — what the whole system is
- `https://callmemaybe.cc/schema/policy.json` — the machine-readable surface
- `https://callmemaybe.cc/schema/trunks.json` — the provider inventory
- `https://callmemaybe.cc/schema/contacts.json` — the address-book inventory
- `doorman schema policy` — the same, from the binary in hand
- `doorman check` — the authority on whether a file is valid
- `doorman init` — generate a starting config whose placeholders cannot
silently work