31 KiB
Board on the three rewrite plans
Junior seat
Junior seat (about 2 years of TypeScript, no SMPP)
My read of main: link-life.ts has 7 predicates over a phase plus a separate stopped flag, and the initial 'up' breaks its own rule. expiring-groups.ts has a header that says what it does not enforce. session.ts routes captureRejections for sms into incoming.listenerRejected. The README's "Receive SMS" section tells me to call sendResp() on a multipart message that "puts nothing on the wire". I agree with 6 overall and Locality 5.
Plan 1: folders follow the README
- Would it help? Marginal, close to yes. Folders named after the README sections are the first layout I could find things in without asking. The problem is answering: I can answer with
sendResp(), by returning, or by throwing, andansweredOnArrivalis still there. That is D's "answered in three places" again, only now behindOwedAnswer. - Predicted scores
- Nav 7: README section maps to folder.
- Loc 6: the answer path spans
receive-message.ts,running-handlers.ts,owed-answer.tsandsms.ts. - Shape 6: the
AnswerPort/SendPortports are indirection I have to learn. - Self 6: citations get summaries, but the glossary sits in the README, and lessons.md says that did not lift juniors.
- Overall 6.
- Where I would still get stuck:
receiving/running-handlers.tstogether withsession/owed-answer.ts. What happens whensendResp()is called and the handler then throws? - Wrong or vague
- The "two SmsSenders" question is left open until chunk 6.
client()returns aClientSessionbut still calls itsession, andsock/sendReturnmove tosession.link. I would not know which object to listen on.- Keeping
sendResp()next to "return answers" gives two ways to answer. - The no-handler refusal turns a transceiver that never wanted inbound messages into an endless SMSC retry loop.
Plan 2: state ownership first
- Would it help? Marginal. It keeps one public
Sessionover many internalLinks, which brings back the thing F removed: the lifecycle is still split over two fields. It addsLinkOwner, five callbacks implemented in another file, which is E's lesson again.session.tsstays the hub for handover, link-wait, reconnect and the merger. - Predicted scores
- Nav 6: the Session versus Link vocabulary.
sms.tssits insession/whilehandlers.tssits inlink/. - Loc 6: there is one answer ledger, but handover is split across
adopt/onGoneand the callbacks. - Shape 6: four request lanes are still four rules.
- Self 6:
wire/fields.tshelps, but the glossary sits away from the code indocs/. - Overall 6.
- Nav 6: the Session versus Link vocabulary.
- Where I would still get stuck:
session/session.tshandover together withlink/link.ts'sLinkOwner. - Wrong or vague
- This plan has the smallest API break of the three, and that is the best part for an application developer.
- The
sendDlr-before-answer error will surprise anyone writing a test SMSC. - The reassembly store refusing its own entry is a behaviour change with no decision written yet.
Plan 3: the newcomer's lens
- Would it help? Yes.
protocol/vocabulary.tsputs TSDoc on hover, and all bit masks stay insideprotocol/.data-coding.tsbecomes a table with a "why" column, and a test fails on any bare citation. That attacks the Self-sufficiency 5 that capped juniors.Replyas a return value makes "one answer" a type. - Predicted scores
- Nav 7: protocol, codec, messages, session is a reading order.
- Loc 6:
client/client.tsgathers the current session, the merge, the reference counter, the retry loop, re-emitting,fromStartand abort. - Shape 7: forward-only session, a store that refuses,
Replyas the one path. - Self 7: definitions at the point of use.
- Overall 6, one refactor short of 7.
- Where I would still get stuck: the request loop and event re-emitting in
client/client.ts. The plan itself predicts this. - Wrong or vague
- It has six breaks. A1 renames
{ session }to{ client }, which breaks every README example for no comprehension gain beyond F, which scored the same as main. - A3's
Reply.dlris a second way to send a receipt next tosendDlr(). - A4 removes
sendReturn(), the escape hatch for hand-wired users. handlerTimeoutappears inhandlers.tsbut is missing from the API table.- Refuse-not-evict can block multipart traffic for
reassemblyTimeout, which regresses goal 4. waiting.tsextracts the abort dance, contradicting a recorded decision without saying so.- A6 (
'ASCII'to'GSM7') is justified for me as a reader, but it is optional.
- It has six breaks. A1 renames
Verdict
- Ranking: Plan 3, Plan 1, Plan 2.
- Can the best reach 7? Plausibly, about a coin flip. The single change that most raises its odds: move the retry loop and the link-wait out of
client/client.tsinto their own file,client/next-link.tsas in Plan 1, soSmppClientonly holds and forwards. Dropping A1 and A3 would also stop the application-developer complaints from costing Shape. - Structure or intrinsic difficulty? Mostly structure. Each round moved the hardness around while the SMPP-to-plain-types translation was never in one place. The truly intrinsic parts are small and can be localized: per-segment answers on arrival, the drain's two budgets, and retrying only what was never written. For a junior, the rest of the ceiling was missing domain vocabulary. That is a structural choice about where meaning lives, not a property of the problem.
PLAN1 helps=marginal nav=7 loc=6 shape=6 self=6 overall=6 PLAN2 helps=marginal nav=6 loc=6 shape=6 self=6 overall=6 PLAN3 helps=yes nav=7 loc=6 shape=7 self=7 overall=6
Mid seat
Mid seat (5 years TypeScript, never read the SMPP spec)
My view of main today: 6. I can find my way around the flat src/, and session.ts is readable at the top. LinkLife is where I stall. It has a phase, a stopped flag, seven predicates and an initial 'up' that breaks its own rule. ExpiringGroups is the other place: its doc comment says it enforces neither of its own bounds, yet weigh() can evict the key the caller is writing. The captureRejections routing to incoming.listenerRejected also takes me three files to follow.
Plan 1: README verbs as folders, a one-socket Session, and ClientSession
- Helps? Marginal, leaning yes. Folders that mirror the README's table of contents are what I would guess first.
owed-answer.tsgives "one answer per request" a single writer, andBoundedStorestops evicting the caller's own entry. But the one-socket split is F's, which already scored 6.25, and the plan leaves its sharpest follow-up open: which object ownsSmsSender, to be settled "at chunk 6". - Predicted scores:
- Navigation 7: the folder names are the README sections.
- Locality 6: an inbound message still crosses
dispatch.ts,receive-message.ts,running-handlers.ts,owed-answer.tsandsms.ts. - Shape 6:
ClientSessionforwards events, and there are twoSmsSenders and two ports. - Self-sufficiency 7: the README glossary, citations with their summaries, and
Invariant:paragraphs. - Overall 6.
- What would still defeat me:
client/client.ts. AClientSessionthat re-emits the currentSession's events, answersboundAsthrough the reconnect gap, and holds a merger fed from the link'sdlr. It is the "which object do I listen on" problem moved up one layer. - Wrong or vague:
client()still resolves{ session }, but the value is aClientSessionandsession.linkis the realSession. That name misleads an application developer.sendResp()is kept, and returning from the handler also answers. That is two ways to answer the same message, andansweredOnArrivalstays as a third concept.limits/is an abstraction name I would not look in for the send window.- The dual
SmsSenderis undecided.
Plan 2: state ownership, with a Link inside the public Session
- Helps? Marginal. The ownership rules are right: one writer, a lifetime
AbortController, and "emit last".wire/fields.tsnaming the bit masks helps me more than any glossary. ButSessionstill spans many links, which is the unit that capped round two, now split acrosssession.tsandlink.ts. They are joined byLinkOwner, a five-callback interface, which is exactly what defeated E. - Predicted scores:
- Navigation 6:
link/againstsession/is a distinction I must learn before I can placehandlers.ts,sms.tsorsend-sms.ts. - Locality 6: the answering path is in two adjacent files, which is good, but a link going down spans
Link,LinkOwner,adopt/onGone,link-wait.tsandoutbound.ts. - Shape 6: four lanes in one table are still four rules.
sockmeans "the current or last Link's", andboundAsis read "from the last bound Link". - Self-sufficiency 6: the glossary lives in
docs/glossary.md, away from the code, and the lessons say an off-code glossary did not lift juniors. - Overall 6.
- Navigation 6:
- What would still defeat me:
session/session.tsadopt/onGone, withsession/outbound.ts's retry loop that goes back toboundLink(). That is the reconnect lifecycle again, under new names. - Wrong or vague:
- It keeps the reconnecting
Sessionto avoid F's rename. That preserves the exact structure every round-two seat named. - Changing
sendReturn()to returnerrin two new cases is a silent behaviour change on an existing call. - The rule "a
sendDlr()before the answer returnserr" breaks test-double servers that report immediately, and the plan admits it. - The five callbacks and the lane table are not specified.
- It keeps the reconnecting
Plan 3: a protocol/ translation layer, Reply return values, and SmppClient
- Helps? Yes. This is the only plan aimed at my actual cost: the bit masks and SMPP terms, translated once in
protocol/into typed plain values, with definitions I see on hover in the editor. A citation test enforces that every spec reference carries its sentence.requests-in.ts replyFor()is a pure function returning aReply, which makes "one answer per request" a type instead of an agreement between files. ItsSessionis one socket with one forward-only state field. - Predicted scores:
- Navigation 7:
codec,protocol,messages,session,client,serverread in order. The weak spots areretained.tsandbounded-store.tsundermessages/. - Locality 7: a reply is decided in one function and written in one place, and data_coding is one table.
- Shape 6: two
sendSmssurfaces,Reply.dlrbesidesendDlr(), and aSmppClienthub. - Self-sufficiency 7: definitions next to their use, and every citation says what it cites.
- Overall 7.
- Navigation 7:
- What would still defeat me:
client/client.ts. It holds the current session, the receipt merge, the concatenation reference counter, the retry-if-unwritten request loop, event re-emission,close/unbind, andfromStartplus abort. That is seven responsibilities in one file, and the plan itself predicts it becomes the next hardest unit. - Wrong or vague:
- Six breaking changes is more than "a minimum". A6 (
'ASCII'renamed to'GSM7') breaks every caller that set an encoding. It is justified by one-spelling-per-goal but not required for comprehension, since the internal rename already gets that gain. - A3's
Reply.dlris a second way to send a receipt. - Refusing new segments when the reassembly store is full, instead of evicting the oldest group, lets abandoned groups block all multipart traffic until
reassemblyTimeout. That hurts an operator (goal 4), and the plan does not weigh it against the goal 2 gain. - Removing
sendReturn()takes away an escape hatch for low-level users without saying what replaces it beyond "return aReply". retained.tssits inmessages/only because the stores use it. That is proximity, not a real seam.
- Six breaking changes is more than "a minimum". A6 (
Verdict
Ranking: Plan 3, then Plan 1, then Plan 2.
Can the best reach 7 overall? Plausibly on my seat. A panel mean of 7 is less likely, because the architect seat will cite the SmppClient hub and the number of API breaks. The single change that would most raise its odds is to split SmppClient's request loop into its own file (client/next-link.ts, as in Plan 1), owning waiting-for-a-bound-session and retry-only-unwritten with its invariant. client.ts then only composes, and the predicted next hardest unit never forms.
Structure or intrinsic difficulty? Mostly structure, and specifically the contract that structure was built around. Each round, the named hardest unit was self-inflicted rather than SMPP:
- the held-message timing contract;
- a reconnecting session with duplicated stop flags;
- a store that does not enforce its own bounds;
- answering enforced jointly by two files.
When the contract changed, the unit moved, and none of the ones named since are protocol facts. The intrinsic part — multipart answered on arrival, the drain's two budgets, retrying only what never reached the socket, and data_coding groups — is real. But it is a handful of localized items, which puts the ceiling near 7–8, not 6. The earlier redesigns stalled because each one left a different piece of shared, unowned state behind.
PLAN1 helps=marginal nav=7 loc=6 shape=6 self=7 overall=6 PLAN2 helps=marginal nav=6 loc=6 shape=6 self=6 overall=6 PLAN3 helps=yes nav=7 loc=7 shape=6 self=7 overall=7
Senior seat
Plan 3 is the only one I expect to reach 7. Plan 1 is a marginal gain and plan 2 is roughly main plus renames. My seat most likely already gave main its 7, so a rewrite has to beat 7 on this seat to count as help here.
How hard main is today, from my seat. link-life.ts shows lesson 3 exactly. It has a 4-value phase starting at 'up', a separate stopped, and seven predicates. Its generation() counter exists only so a reader can tell a link has gone. session.ts (453 lines) wires ten collaborators. Its captureRejectionSymbol override reaches into incoming.listenerRejected. The layout is flat and named well, so Navigation is fine. The cost is in Locality: to know whether a send is safe you need LinkLife, OutgoingRequests and Session open together.
Plan 1: verb folders, one-socket Session, ClientSession above it, onSms plus sendResp kept
1. Would it help? Marginal.
- The one-way
Session, the reconnect union state,BoundedStoreenforcing its own bounds and one defaults file all remove units the panels named. - It adds new sources of confusion in their place:
client()still returns a field calledsessionthat is aClientSession, andsession.linkis aSession. Two names now lie.- Answering has two spellings. The handler can call
sendResp()or just return, and the return path does "answerESME_ROKif not answered". That is D's "answered in several places" again, now as a getter over OwedAnswers. - The answer path spans five files in two folders:
dispatch.ts,owed-answer.ts,receive-message.ts,running-handlers.tsandsms.ts.
2. Predicted scores
- Navigation 7. The README table of contents mirrors the folder listing, which makes the layout easy to find your way around.
limits/is the one abstract name. - Locality 6. The one-answer invariant has one writer, but you cannot understand it without the other four files.
AnswerPortandSendPortadd a hop. - Shape 6.
ClientSessionforwards a long event list. Whether there is oneSmsSenderor two is left open. - Self-sufficiency 7. Every citation gets a summary, the README gets a glossary, and there is a data-coding table.
- Overall 7, one point above the lowest dimension (Locality 6), which is the most the rule allows. It is no better than main on this seat.
3. Where I would still get stuck. client/client.ts together with client/next-link.ts: ClientSession forwarding events from whichever Session is current, plus answering boundAs through the reconnect gap. Second place: session/owed-answer.ts together with receiving/running-handlers.ts.
4. Wrong or vague
SmsSenderis left as "pick one at chunk 6". That is an ownership question, and the plan's own principle says ownership comes first.- Returning
ClientSessionunder the namesessionis a public API that misleads the developer about what they hold. Either rename it honestly, as plan 3 does, or keep a singleSession. - Keeping
sendResp()alongside the implicit return answer gives two ways to answer, against the one-spelling rule. linkEndbecoming readonly is justified.
Plan 2: state ownership, a Link per socket behind the unchanged public Session
1. Would it help? Marginal.
- The strongest ownership rules of the three: one
AbortControlleras the single writer of stoppedness, and "a transition finishes before anyone hears about it". - The smallest API break, with a single answering spelling (
onSmsreturning{smsId}/{status}) and a ledger that refuses a second answer. - But its structure is draft A (a
Linkobject per socket) plus E's weakness (LinkOwner, five callbacks implemented in another file). Lesson 3 of the round-three notes already says E's state machine stayed hard because meaning was split across callbacks. session.tsstays the hub: life, current link, handover, link-wait, window, reconnect, merger.outbound.tskeeps four "lanes", which were already cited in round 2.
2. Predicted scores
- Navigation 6. Readers must learn the Session/Link split.
link/holds handlers and reassembly, which a reader would not look for there. - Locality 6. A handover is
adopt/onGoneinsession.tsplus theLinkOwnercallbacks inlink.ts. Each transition's meaning is split across two files. - Shape 6. Four lanes, a hub
Session, and a new "refuse own entry" eviction policy. - Self-sufficiency 7.
wire/fields.tsnames the bit masks, and every citation gets its sentence. - Overall 6.
3. Where I would still get stuck. session/session.ts (adopt/onGone and the link handover) read against link/link.ts's LinkOwner. Second place: the lane table in session/outbound.ts.
4. Wrong or vague
sendDlr()returningerrbefore the answer is a trap for test SMSCs that report immediately. The plan admits this and gives only a README pattern as the fix.- The own-entry refusal changes reassembly behaviour under pressure without a stated goal trade-off.
- It is unclear where
idle-waitersends up: the plan says "inlined" in two places. - It never says whether
generation()'s replacement ("a message holds its Link") keeps a goneLinkalive in memory.
Plan 3: a protocol/ translation layer, answers as Reply return values, SmppClient above a one-socket Session
1. Would it help? Yes.
- It is the only plan that makes "one answer per request" a type.
replyFor()returns aReply, andSession.write()is the single writer.onRequestalso returns aReply, andsendReturnis gone. - Bit masks never leave
protocol/, and a test fails on any bare§x.y.zcitation. That targets the junior Self-sufficiency cap directly. BoundedStoreis kept simple.- It removes the lifecycle cap the same way F did.
2. Predicted scores
- Navigation 7. The areas read in order: protocol → codec → messages → session → client. The only open question is which
sendSmsto call. - Locality 7. Answering, the lifecycle and the drain each live in one function. The exception is the
client.tshub. - Shape 7. State sits in three named files and reconnect lives above the socket.
Reply.dlris a wart. - Self-sufficiency 7. The glossary is in code, shown on hover, and citations are enforced by a test.
field-types.tsstays dense. - Overall 7.
3. Where I would still get stuck. client/client.ts. It holds the current session, the receipt merge, the segment reference counter, the request loop that retries only unwritten requests, event re-emitting, close/unbind and fromStart plus abort. That is F's SmppClient with more loaded onto it, and the lessons predict the hardest unit lands here next.
4. Wrong or vague
- The async path is not described.
replyFor()is described as pure, butonSmsis asynchronous. The plan never names the one function that carries a message fromreplyForthroughhandlers.tstosession.write. Without it, "exactly one answer" spreads back over three files. - A6 (
'ASCII'renamed to'GSM7') is unjustified. It breaks every caller for a name the code can gloss once, and goal 8 favours a stable surface. - A3 (
Reply.dlr) adds a second way to send a receipt besidesms.sendDlr(). - Refuse-not-evict for reassembly lets a peer that abandons segment groups block all multipart traffic for
reassemblyTimeout. That is an operator-facing regression under goal 4, and "goal 2 served better" does not hold for traffic we refuse and the peer then gives up on. - A1 renames
sessiontoclientonclient()'s result. That is defensible: it is honest where plan 1 is not, but it is a real migration cost. - It is silent on goal 9 (a store interface).
BoundedStore's shape should not make that goal harder later.
Verdict
Ranking: plan 3, then plan 1, then plan 2.
Can the best reach 7 overall? Plausibly yes, as a mean around 6.75 to 7, if it drops A3 and A6. The single change that would most raise its odds: move the retry-only-unwritten request loop out of client/client.ts into its own file, like plan 1's client/next-link.ts, with its invariant at the top. The same file or function should also carry the async onSms reply path, so that neither SmppClient nor the answer path becomes the next hardest unit.
Structure or intrinsic difficulty? Mostly structure.
- The hardest unit moved every round: the held-message flow, then the lifecycle, then
ExpiringGroupsand the answer invariant. Intrinsic difficulty does not move when you reorganise, so a cap that moves each time is coming from coupling. - Every draft so far kept at least one object that held both the socket and what outlives the socket, or both the answer and its trigger. The panel scores that worst unit.
- The intrinsic core does set a floor: answering each segment on arrival, the drain's two budgets, retrying only what was never written, and
data_coding. That floor is about 7 and is not what capped the drafts at 6. - The coarse four-seat integer scale explains why every drop in difficulty looked like no movement at all.
PLAN1 helps=marginal nav=7 loc=6 shape=6 self=7 overall=7 PLAN2 helps=marginal nav=6 loc=6 shape=6 self=7 overall=6 PLAN3 helps=yes nav=7 loc=7 shape=7 self=7 overall=7
Architect seat
Board seat: inherited architect. I read link-life.ts (a four-value phase plus stopped, seven predicates, and an initial 'up'), expiring-groups.ts (which says outright that it "enforces neither max nor timeout itself") and session.ts. My view matches the panel: 6 overall, Locality 6. The hard spots are ones the code created, not ones SMPP forces.
Plan 1: folders named after what the developer does, ClientSession above a one-socket Session
- Would it help? Marginal. It combines F's one-socket split with D's handler that keeps
sendResp(), and both scored 6 before. Answering is now spread over four files in two folders:session/owed-answer.ts,receiving/receive-message.ts,receiving/running-handlers.tsandreceiving/sms.ts. The ports (AnswerPort,SendPort) add names without removing a step. - Predicted scores:
- Nav 6: folders named after what the developer does are good. But
client()still resolves{ session }, and that value is aClientSessionwhose.linkis the realSession, so the name lies at the first line of every example.limits/is a catch-all name. - Loc 6: the one-answer rule has one writer, but the path to that writer crosses two folders through ports.
- Shape 6:
sendResp()and returning from the handler are two ways to answer. "TwoSmsSenders in client mode" is left unresolved. - Self 6: the glossary goes in the README, which the lessons say did not lift juniors.
- Overall 6.
- Nav 6: folders named after what the developer does are good. But
- Where it still defeats me:
client/client.ts. It re-emits the current link's events, answersboundAsthrough the reconnect gap and owns the merger across links, whileclient/next-link.tsretries underneath it. This is F'sSmppClientagain. - Wrong or vague:
- Keeping the name
sessionfor aClientSessionis harmful. An app developer callssession.sockorsession.sendReturn()and finds them moved tosession.link. - Which object owns the
SmsSenderis left open "until chunk 6", and that is the part most likely to rot. sendResp()plus answer-on-return breaks the one-spelling rule.- Making
linkEndreadonly is fine.
- Keeping the name
Plan 2: every piece of state has one owner, a private Link per socket behind the public Session
- Would it help? Marginal, leaning yes. The ownership discipline is the most honest of the three: a phase that only moves forward, one
AbortControlleras the only stop signal, andlink/answers.tsrefusing a second answer. ButSessionstays the hub (current link, link wait, send window, reconnect, merger, handover).Linkreports to it through a five-callbackLinkOwner, which is the same shape E's panel called hard. The retry still spans links, insession/outbound.tswith four lanes. - Predicted scores:
- Nav 6: a
SessionversusLinkvocabulary gap. Held messages and reassembly underlink/surprise a reader who thinks of them as message concerns. - Loc 6: a transition's meaning is split between
link.tsand theLinkOwnercallbacks implemented insession.ts. - Shape 7: one writer per piece of state, and invariants stated at their owner.
- Self 6:
wire/fields.tsnames the bit masks, but the glossary sits indocs/, away from where the terms are used. - Overall 6.
- Nav 6: a
- Where it still defeats me:
session/session.ts, inadopt()/onGone()together withsession/outbound.ts's loop overboundLink(). That is the reconnect lifecycle kept inside the public unit, only renamed. - Wrong or vague:
- It keeps reconnect inside
Session, which the lessons show is where the ceiling sits. The plan says the rename toSmppClient"bought nothing a reader scores", but F's gain was exactly the removal of the lifecycle from the list of hardest units. - The four lanes stay four rules.
- Refusing the incoming segment when a store is full is a behaviour change under pressure, and the plan argues it only as a risk.
- The API changes are the most conservative:
sendResp()becomes the handler's return value,sendDlr()before the answer returnserr, and a doublesendReturn()returnserr. All are justified.
- It keeps reconnect inside
Plan 3: plain-English protocol types, answers as return values, SmppClient above a one-socket Session
- Would it help? Yes. It is the only plan that attacks all three standing caps at once:
- Lifecycle: one socket per
Session, with reconnect inSmppClient. - Answering: an answer is a
Replyvalue,requests-in.tsis a pure function, andsession.write()is the single writer. "Exactly one answer" becomes something the type system enforces. - Self-sufficiency:
protocol/vocabulary.tsgives definitions on hover,data-coding.tsbecomes a table checked against today's code on all 256 values, and a test fails on any bare spec citation. Of the three, this is the one that actually changes a junior's Self-sufficiency.
- Lifecycle: one socket per
- Predicted scores:
- Nav 7: the folder order (
protocol→codec→messages→session→client) tells you where a question is answered. - Loc 6:
client/client.tsgathers the current session, state kept across links, the retry loop, event re-emitting,fromStartand abort in one file. - Shape 7: pure
replyFor(), a store that refuses rather than evicts and never mutates on read, and a lifecycle that only moves forward. - Self 7: vocabulary beside the code, and the citation rule enforced by a test.
- Overall 7, but only just.
- Nav 7: the folder order (
- Where it still defeats me:
client/client.ts(SmppClient). The request loop waits for a bound session across reconnects, retries only what was never written, and re-emits events, all next to thefromStartand abort handling. The plan's own risk list predicts this file.session/handlers.tsconverting a handler's outcome into aReplyfor a message already answered on arrival comes second. - Wrong or vague:
- Six breaking changes where two carry the value.
- A3 (
Reply.dlr) is a second way to send a receipt besidesendDlr(). It is unjustified; drop it. - A6 (
'ASCII'→'GSM7') breaks every caller for a name that can be glossed once inside the library. Drop it. - A1 (
{ client }in place of{ session }) is justified, and it is more honest than plan 1'ssessionthat is really a client.
- A3 (
- Refuse-not-evict means a peer that abandons segment groups blocks all new multipart traffic for
reassemblyTimeout. The goal 4 regression is argued only in one direction. retained.tsandbounded-store.tsinmessages/are misfiled, since neither is message logic.- "Re-emits session events" does not say which events or how.
- Six breaking changes where two carry the value.
Verdict
Ranking: Plan 3, then Plan 2, then Plan 1.
Does plan 3 reach 7? Plausibly. My odds are about even, because Locality 6 is the cap and the overall may not exceed it by more than one. The single change that most raises the odds: move SmppClient's bound-session wait and unwritten-only retry out of client/client.ts into a file of their own (plan 1's client/next-link.ts shape) with its Invariant: paragraph. client.ts is then only composition and state carried across links, and the unit the panel will name next is small and marked. Dropping A3 comes second.
Structure or intrinsic difficulty? Structure, including the structure the public contract forced. Every unit the rounds named was one the code created, not the protocol:
- the held-message timing contract;
LinkLife's predicates and its duplicate stop flag;ExpiringGroupsleaving its bounds to its callers;- one answer enforced jointly by two files.
The truly hard SMPP parts are few and can be kept in one place each: segments answered on arrival, the drain's two budgets, retrying only what never reached the socket, data_coding groups and operator receipt spellings. A 7 allows exactly that. The first two rounds failed because internals were rearranged under a contract that pinned the hard spot in place. The later rounds each removed a created unit and exposed the next one. Nothing yet shows that SMPP itself caps the scores at 6.
PLAN1 helps=marginal nav=6 loc=6 shape=6 self=6 overall=6 PLAN2 helps=marginal nav=6 loc=6 shape=7 self=6 overall=6 PLAN3 helps=yes nav=7 loc=6 shape=7 self=7 overall=7